[{"data":1,"prerenderedAt":5970},["ShallowReactive",2],{"page-\u002Fmedia\u002Fwmf完成高性能的音频处理":3},{"id":4,"title":5,"body":6,"description":5959,"extension":5960,"meta":5961,"navigation":440,"path":5966,"seo":5967,"stem":5968,"__hash__":5969},"content\u002Fmedia\u002FWMF完成高性能的音频处理.md","WMF完成高性能的音频处理",{"type":7,"value":8,"toc":5923},"minimark",[9,25,72,98,101,106,117,122,141,179,207,211,222,253,260,262,265,272,291,314,326,352,1636,1641,1693,1695,1702,1722,1724,1730,1761,2243,2256,2258,2269,2283,2292,2304,2331,2401,2411,2413,2419,2435,2441,2478,2500,2545,2573,2621,2623,2627,2684,2696,3218,3221,3259,3262,3288,3330,3333,3336,3409,3412,3419,3508,3517,3520,3523,3652,3655,3658,3660,3666,4061,4070,4072,4080,4108,4115,4212,4255,4259,5043,5046,5049,5052,5122,5125,5132,5442,5445,5452,5455,5546,5549,5553,5608,5610,5614,5679,5681,5688,5714,5747,5834,5836,5840,5879,5885,5887,5891,5917,5919],[10,11,12,13,17,18,24],"p",{},"在 Windows 平台上，",[14,15,16],"strong",{},"Media Foundation（常口头叫 WMF\u002FMF）"," 是比 FFmpeg 更「原生」的一套管线：系统 DLL + 可选显卡驱动的 MFT（Media Foundation Transform）。你可以用 ",[14,19,20],{},[21,22,23],"code",{},"IMFSinkWriter"," 把视频轨、音频轨mux进 MP4。",[10,26,27,28,31,32,35,36,39,40,43,44,49,50,59,60,63,64,67,68,71],{},"但要注意一句话：",[14,29,30],{},"不是","你一写 SinkWriter，系统就必然走 ",[14,33,34],{},"NVENC \u002F Quick Sync"," 硬件编码——能不能用上硬件 H.264\u002FAAC MFT，取决于 ",[14,37,38],{},"SinkWriter 创建的拓扑","、",[14,41,42],{},"你注册的输出 subtype","、以及 ",[14,45,46],{},[21,47,48],{},"MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS"," 等属性。我们自己在工程里（见下文 ",[14,51,52,55,56],{},[21,53,54],{},"WasapiLoopback.cpp"," 里 ",[21,57,58],{},"MediaRecorder::StartRecording","）为了避开「某些机型驱动里硬件 MFT 抽风」，",[14,61,62],{},"默认把硬件变换关掉，优先软件编码保证可录","；视频侧仍尽量走 ",[14,65,66],{},"DXGI Surface → MF"," 的 GPU 路径喂帧，必要时再 ",[14,69,70],{},"回退到 CPU Map\u002FRGB 拷贝","。这样既保留 MF 的原生性，又不会在用户机上随机 HRESULT。",[10,73,74,75,43,78,81,82,39,87,39,92,97],{},"下面行文把你关心的 ",[14,76,77],{},"WASAPI loopback → PCM → SinkWriter → AAC",[14,79,80],{},"时间戳 \u002F 软硬回退"," 串起来；代码片段尽量与仓库 ",[14,83,84],{},[21,85,86],{},"glfwExample\u002FWasapiLoopback.cpp",[14,88,89],{},[21,90,91],{},"media_recorder.h",[14,93,94],{},[21,95,96],{},"main_n.cpp"," 对齐。",[99,100],"hr",{},[102,103,105],"h2",{"id":104},"wasapi-录制与时间轴","WASAPI 录制与时间轴",[10,107,108,109,112,113,116],{},"录制里一等公民是 ",[14,110,111],{},"A\u002FV Sync","。视频帧是离散的，音频是连续的；采集时钟（声卡混音、QPC）和 MF 内部时钟都要落到同一套 ",[14,114,115],{},"100ns"," 时间轴上。",[118,119,121],"h3",{"id":120},"loopback-与格式","Loopback 与格式",[10,123,124,125,134,135,140],{},"我们需要 ",[14,126,127,130,131],{},[21,128,129],{},"IAudioClient"," + ",[21,132,133],{},"IAudioCaptureClient","，并用 ",[14,136,137],{},[21,138,139],{},"AUDCLNT_STREAMFLAGS_LOOPBACK"," 捕获「扬声器\u002F指定播放设备上听到的」混音，而不是麦克风。",[10,142,143,144,147,148,151,152,155,156,159,160,164,165,168,169,174,175,178],{},"混音格式多为 ",[14,145,146],{},"IEEE Float","（",[21,149,150],{},"WAVE_FORMAT_IEEE_FLOAT"," \u002F ",[21,153,154],{},"WAVEFORMATEXTENSIBLE","），而我们在 SinkWriter 里给 AAC 配的输入一般是 ",[14,157,158],{},"16-bit PCM","。因此在 ",[14,161,162],{},[21,163,96],{}," 的回调里会做一层 ",[14,166,167],{},"float → int16","（clamp 到 ±1.0 再乘 32767），再 ",[14,170,171],{},[21,172,173],{},"PushAudioData","——笔记后面 ",[14,176,177],{},"「PCM 与编码入口」"," 一节会点到这条链。",[10,180,181,182,186,187,192,193,198,199,202,203,206],{},"使用 ",[14,183,184],{},[21,185,23],{}," 时，视频一侧塞 ",[14,188,189],{},[21,190,191],{},"ID3D11Texture2D","（尽量 ",[14,194,195],{},[21,196,197],{},"MFCreateDXGISurfaceBuffer","），音频一侧塞 ",[14,200,201],{},"PCM 块","；SinkWriter 内部会拉起编码 MFT，把 ",[14,204,205],{},"H.264 \u002F AAC"," 写进容器。",[118,208,210],{"id":209},"ptspresentation-time","PTS（Presentation Time）",[10,212,213,214,217,218,221],{},"MF 的时间单位是 ",[14,215,216],{},"100 纳秒（1s = 10⁷ 单位）","。若统一用 ",[14,219,220],{},"QPC"," 做「录制起点」：",[223,224,225,241],"ul",{},[226,227,228,229,234,235,240],"li",{},"记 ",[14,230,231],{},[21,232,233],{},"T₀"," 为第一次有效音视频写入时的 QPC（工程里用 ",[14,236,237],{},[21,238,239],{},"m_startTimeQPC","）。",[226,242,243,246,247,252],{},[14,244,245],{},"PTS（100ns）"," ≈ ",[14,248,249],{},[21,250,251],{},"(QPC_now − T₀) × 10⁷ \u002F QPC_frequency","。",[10,254,255,256,259],{},"音频也可以按「累计采样数 \u002F 采样率」推 PTS，但我们在工程里与视频共用 ",[14,257,258],{},"QPC 差分","，实现简单、和 WGC 帧同源。",[99,261],{},[102,263,264],{"id":264},"编写代码",[118,266,268,269,271],{"id":267},"头文件与类与当前-media_recorderh-一致","头文件与类（与当前 ",[21,270,91],{}," 一致）",[10,273,274,275,39,280,285,286,290],{},"除了 ",[14,276,277],{},[21,278,279],{},"Audioclient.h",[14,281,282],{},[21,283,284],{},"wrl\u002Fclient.h","，实际工程里 ",[14,287,288],{},[21,289,91],{}," 还拉了 MF 读写、DXGI 设备管理器、以及设备属性键（若你做枚举）。核心接口仍是：",[223,292,293,300,307],{},[226,294,295,299],{},[14,296,297],{},[21,298,129],{},"：流初始化、Start\u002FStop。",[226,301,302,306],{},[14,303,304],{},[21,305,133],{},"：从驱动拉包。",[226,308,309,313],{},[14,310,311],{},[21,312,139],{},"：loopback 混音。",[10,315,316,321,322,325],{},[14,317,318],{},[21,319,320],{},"Microsoft::WRL::ComPtr","：COM RAII，离开作用域自动 ",[21,323,324],{},"Release","，避免漏释。",[10,327,328,329,333,334,39,339,344,345,351],{},"下面这段结构体\u002F类声明请与仓库 ",[14,330,331],{},[21,332,91],{}," 对齐（含 ",[14,335,336],{},[21,337,338],{},"enableAudio",[14,340,341],{},[21,342,343],{},"audioOutputDeviceId"," 等；",[14,346,347,350],{},[21,348,349],{},"WasapiLoopback::Initialize"," 可带设备 ID","）：",[353,354,359],"pre",{"className":355,"code":356,"language":357,"meta":358,"style":358},"language-cpp shiki shiki-themes github-light github-dark","#pragma once\n#pragma comment(lib, \"mfplat.lib\")\n#pragma comment(lib, \"mfuuid.lib\")\n#pragma comment(lib, \"mfreadwrite.lib\")\n\n#include \u003Cwindows.h>\n#include \u003Cunknwn.h>\n#include \u003Cwrl\u002Fclient.h>\n#include \u003CAudioclient.h>\n\n#include \u003Cmfapi.h>\n#include \u003Cmfidl.h>\n#include \u003Cmmdeviceapi.h>\n#include \u003Cendpointvolume.h>\n#include \u003CFunctiondiscoverykeys_devpkey.h>\n\n#include \u003Cmfreadwrite.h>\n#include \u003Cmfobjects.h>\n#include \u003Cavrt.h>\n\n#include \u003Cd3d11.h>\n\n#include \u003Cfunctional>\n#include \u003Cmemory>\n#include \u003Cstring>\n#include \u003Cthread>\n#include \u003Ccstdint>\n#include \u003Catomic>\n\nusing Microsoft::WRL::ComPtr;\nusing AudioDataCallback = std::function\u003Cvoid(const uint8_t*, size_t*, uint64_t*)>;\n\nstruct RecorderConfig {\n    uint32_t width;\n    uint32_t height;\n    uint32_t fps;\n    uint32_t videoBitrate = 10000000;\n    uint32_t audioSampleRate = 48000;\n    uint32_t audioChannels = 2;\n    \u002F** false：仅 H.264，无 AAC，可绕过部分机型 AAC\u002FPCM 协商失败 *\u002F\n    bool enableAudio = true;\n    \u002F** 空：默认播放设备；否则为 IMMDevice::GetId() 同款的设备实例路径字符串 *\u002F\n    std::wstring audioOutputDeviceId;\n};\n\nclass MediaRecorder {\npublic:\n    MediaRecorder(ComPtr\u003CID3D11Device> device, const RecorderConfig& config);\n    ~MediaRecorder();\n\n    uint64_t GetQPCFrequency();\n    uint64_t GetCurrentQPC();\n\n    HRESULT StartRecording(const std::wstring& outputPath, const RecorderConfig& config);\n    HRESULT PushVideoFrame(ComPtr\u003CID3D11Texture2D> texture, uint64_t qpcTime);\n    HRESULT PushAudioData(const uint8_t* data, size_t size, uint64_t qpcTime);\n    void StopRecording();\n\n    std::atomic\u003Cbool> m_isRecording{ false };\n    uint64_t m_startTimeQPC = 0;\n    uint64_t m_qpcFrequency = 0;\n\nprivate:\n    HRESULT SetupVideoStreaming(const RecorderConfig& config);\n    HRESULT SetupAudioStreaming(const RecorderConfig& config);\n    HRESULT PushVideoFrame_RgbMemory(ComPtr\u003CID3D11Texture2D> texture, LONGLONG pts, LONGLONG frameDur100ns);\n\n    ComPtr\u003CID3D11Device> m_pDevice;\n    ComPtr\u003CID3D11DeviceContext> m_pImmediateContext;\n    ComPtr\u003CID3D11Texture2D> m_stagingReadback;\n    bool m_useRgbMemoryPath = false;\n    ComPtr\u003CIMFDXGIDeviceManager> m_pDeviceManager;\n    ComPtr\u003CIMFSinkWriter> m_sinkWriter;\n\n    RecorderConfig m_config;\n    DWORD m_videoStreamIndex = 0;\n    DWORD m_audioStreamIndex = 0;\n    bool m_hasAudioStream = false;\n};\n\nclass WasapiLoopback {\npublic:\n    WasapiLoopback();\n    ~WasapiLoopback();\n\n    WasapiLoopback(const WasapiLoopback&) = delete;\n    WasapiLoopback& operator=(const WasapiLoopback&) = delete;\n\n    \u002F** deviceId 为空或 nullptr：默认渲染端点（控制台）；否则 GetDevice(deviceId) *\u002F\n    HRESULT Initialize(LPCWSTR deviceId = nullptr);\n\n    void Start(AudioDataCallback callback);\n    void Stop();\n\n    WAVEFORMATEX* GetFormat() const { return m_pwfx; }\n\nprivate:\n    void CaptureThread();\n    ComPtr\u003CIAudioClient> m_audioClient;\n    ComPtr\u003CIAudioCaptureClient> m_captureClient;\n    WAVEFORMATEX* m_pwfx = nullptr;\n    AudioDataCallback m_onData;\n    std::thread m_captureThread;\n    std::atomic\u003Cbool> m_isRunning{ false };\n    uint32_t m_frameSize = 0;\n};\n","cpp","",[21,360,361,374,399,417,435,442,451,459,467,475,480,488,496,504,512,520,525,533,541,549,554,562,567,575,583,591,599,607,615,620,638,684,689,701,710,718,726,744,759,774,781,797,803,817,823,828,839,845,883,892,897,908,918,923,959,990,1023,1034,1039,1063,1078,1092,1097,1103,1123,1143,1180,1185,1198,1211,1223,1238,1251,1263,1268,1277,1292,1306,1320,1325,1330,1340,1345,1353,1361,1366,1389,1417,1422,1428,1451,1456,1474,1484,1489,1515,1520,1525,1535,1547,1559,1575,1584,1597,1617,1631],{"__ignoreMap":358},[362,363,366,370],"span",{"class":364,"line":365},"line",1,[362,367,369],{"class":368},"szBVR","#pragma",[362,371,373],{"class":372},"sScJk"," once\n",[362,375,377,379,382,386,389,392,396],{"class":364,"line":376},2,[362,378,369],{"class":368},[362,380,381],{"class":372}," comment",[362,383,385],{"class":384},"sVt8B","(",[362,387,388],{"class":372},"lib",[362,390,391],{"class":384},", ",[362,393,395],{"class":394},"sZZnC","\"mfplat.lib\"",[362,397,398],{"class":384},")\n",[362,400,402,404,406,408,410,412,415],{"class":364,"line":401},3,[362,403,369],{"class":368},[362,405,381],{"class":372},[362,407,385],{"class":384},[362,409,388],{"class":372},[362,411,391],{"class":384},[362,413,414],{"class":394},"\"mfuuid.lib\"",[362,416,398],{"class":384},[362,418,420,422,424,426,428,430,433],{"class":364,"line":419},4,[362,421,369],{"class":368},[362,423,381],{"class":372},[362,425,385],{"class":384},[362,427,388],{"class":372},[362,429,391],{"class":384},[362,431,432],{"class":394},"\"mfreadwrite.lib\"",[362,434,398],{"class":384},[362,436,438],{"class":364,"line":437},5,[362,439,441],{"emptyLinePlaceholder":440},true,"\n",[362,443,445,448],{"class":364,"line":444},6,[362,446,447],{"class":368},"#include",[362,449,450],{"class":394}," \u003Cwindows.h>\n",[362,452,454,456],{"class":364,"line":453},7,[362,455,447],{"class":368},[362,457,458],{"class":394}," \u003Cunknwn.h>\n",[362,460,462,464],{"class":364,"line":461},8,[362,463,447],{"class":368},[362,465,466],{"class":394}," \u003Cwrl\u002Fclient.h>\n",[362,468,470,472],{"class":364,"line":469},9,[362,471,447],{"class":368},[362,473,474],{"class":394}," \u003CAudioclient.h>\n",[362,476,478],{"class":364,"line":477},10,[362,479,441],{"emptyLinePlaceholder":440},[362,481,483,485],{"class":364,"line":482},11,[362,484,447],{"class":368},[362,486,487],{"class":394}," \u003Cmfapi.h>\n",[362,489,491,493],{"class":364,"line":490},12,[362,492,447],{"class":368},[362,494,495],{"class":394}," \u003Cmfidl.h>\n",[362,497,499,501],{"class":364,"line":498},13,[362,500,447],{"class":368},[362,502,503],{"class":394}," \u003Cmmdeviceapi.h>\n",[362,505,507,509],{"class":364,"line":506},14,[362,508,447],{"class":368},[362,510,511],{"class":394}," \u003Cendpointvolume.h>\n",[362,513,515,517],{"class":364,"line":514},15,[362,516,447],{"class":368},[362,518,519],{"class":394}," \u003CFunctiondiscoverykeys_devpkey.h>\n",[362,521,523],{"class":364,"line":522},16,[362,524,441],{"emptyLinePlaceholder":440},[362,526,528,530],{"class":364,"line":527},17,[362,529,447],{"class":368},[362,531,532],{"class":394}," \u003Cmfreadwrite.h>\n",[362,534,536,538],{"class":364,"line":535},18,[362,537,447],{"class":368},[362,539,540],{"class":394}," \u003Cmfobjects.h>\n",[362,542,544,546],{"class":364,"line":543},19,[362,545,447],{"class":368},[362,547,548],{"class":394}," \u003Cavrt.h>\n",[362,550,552],{"class":364,"line":551},20,[362,553,441],{"emptyLinePlaceholder":440},[362,555,557,559],{"class":364,"line":556},21,[362,558,447],{"class":368},[362,560,561],{"class":394}," \u003Cd3d11.h>\n",[362,563,565],{"class":364,"line":564},22,[362,566,441],{"emptyLinePlaceholder":440},[362,568,570,572],{"class":364,"line":569},23,[362,571,447],{"class":368},[362,573,574],{"class":394}," \u003Cfunctional>\n",[362,576,578,580],{"class":364,"line":577},24,[362,579,447],{"class":368},[362,581,582],{"class":394}," \u003Cmemory>\n",[362,584,586,588],{"class":364,"line":585},25,[362,587,447],{"class":368},[362,589,590],{"class":394}," \u003Cstring>\n",[362,592,594,596],{"class":364,"line":593},26,[362,595,447],{"class":368},[362,597,598],{"class":394}," \u003Cthread>\n",[362,600,602,604],{"class":364,"line":601},27,[362,603,447],{"class":368},[362,605,606],{"class":394}," \u003Ccstdint>\n",[362,608,610,612],{"class":364,"line":609},28,[362,611,447],{"class":368},[362,613,614],{"class":394}," \u003Catomic>\n",[362,616,618],{"class":364,"line":617},29,[362,619,441],{"emptyLinePlaceholder":440},[362,621,623,626,629,632,635],{"class":364,"line":622},30,[362,624,625],{"class":368},"using",[362,627,628],{"class":372}," Microsoft",[362,630,631],{"class":384},"::",[362,633,634],{"class":372},"WRL",[362,636,637],{"class":384},"::ComPtr;\n",[362,639,641,643,646,649,652,654,657,660,663,665,668,671,673,676,678,681],{"class":364,"line":640},31,[362,642,625],{"class":368},[362,644,645],{"class":372}," AudioDataCallback",[362,647,648],{"class":368}," =",[362,650,651],{"class":372}," std",[362,653,631],{"class":384},[362,655,656],{"class":372},"function",[362,658,659],{"class":384},"\u003C",[362,661,662],{"class":368},"void",[362,664,385],{"class":384},[362,666,667],{"class":368},"const",[362,669,670],{"class":368}," uint8_t*",[362,672,391],{"class":384},[362,674,675],{"class":368},"size_t*",[362,677,391],{"class":384},[362,679,680],{"class":368},"uint64_t*",[362,682,683],{"class":384},")>;\n",[362,685,687],{"class":364,"line":686},32,[362,688,441],{"emptyLinePlaceholder":440},[362,690,692,695,698],{"class":364,"line":691},33,[362,693,694],{"class":368},"struct",[362,696,697],{"class":372}," RecorderConfig",[362,699,700],{"class":384}," {\n",[362,702,704,707],{"class":364,"line":703},34,[362,705,706],{"class":368},"    uint32_t",[362,708,709],{"class":384}," width;\n",[362,711,713,715],{"class":364,"line":712},35,[362,714,706],{"class":368},[362,716,717],{"class":384}," height;\n",[362,719,721,723],{"class":364,"line":720},36,[362,722,706],{"class":368},[362,724,725],{"class":384}," fps;\n",[362,727,729,731,734,737,741],{"class":364,"line":728},37,[362,730,706],{"class":368},[362,732,733],{"class":384}," videoBitrate ",[362,735,736],{"class":368},"=",[362,738,740],{"class":739},"sj4cs"," 10000000",[362,742,743],{"class":384},";\n",[362,745,747,749,752,754,757],{"class":364,"line":746},38,[362,748,706],{"class":368},[362,750,751],{"class":384}," audioSampleRate ",[362,753,736],{"class":368},[362,755,756],{"class":739}," 48000",[362,758,743],{"class":384},[362,760,762,764,767,769,772],{"class":364,"line":761},39,[362,763,706],{"class":368},[362,765,766],{"class":384}," audioChannels ",[362,768,736],{"class":368},[362,770,771],{"class":739}," 2",[362,773,743],{"class":384},[362,775,777],{"class":364,"line":776},40,[362,778,780],{"class":779},"sJ8bj","    \u002F** false：仅 H.264，无 AAC，可绕过部分机型 AAC\u002FPCM 协商失败 *\u002F\n",[362,782,784,787,790,792,795],{"class":364,"line":783},41,[362,785,786],{"class":368},"    bool",[362,788,789],{"class":384}," enableAudio ",[362,791,736],{"class":368},[362,793,794],{"class":739}," true",[362,796,743],{"class":384},[362,798,800],{"class":364,"line":799},42,[362,801,802],{"class":779},"    \u002F** 空：默认播放设备；否则为 IMMDevice::GetId() 同款的设备实例路径字符串 *\u002F\n",[362,804,806,809,811,814],{"class":364,"line":805},43,[362,807,808],{"class":372},"    std",[362,810,631],{"class":384},[362,812,813],{"class":372},"wstring",[362,815,816],{"class":384}," audioOutputDeviceId;\n",[362,818,820],{"class":364,"line":819},44,[362,821,822],{"class":384},"};\n",[362,824,826],{"class":364,"line":825},45,[362,827,441],{"emptyLinePlaceholder":440},[362,829,831,834,837],{"class":364,"line":830},46,[362,832,833],{"class":368},"class",[362,835,836],{"class":372}," MediaRecorder",[362,838,700],{"class":384},[362,840,842],{"class":364,"line":841},47,[362,843,844],{"class":368},"public:\n",[362,846,848,851,853,856,858,861,864,868,870,872,874,877,880],{"class":364,"line":847},48,[362,849,850],{"class":372},"    MediaRecorder",[362,852,385],{"class":384},[362,854,855],{"class":372},"ComPtr",[362,857,659],{"class":384},[362,859,860],{"class":372},"ID3D11Device",[362,862,863],{"class":384},"> ",[362,865,867],{"class":866},"s4XuR","device",[362,869,391],{"class":384},[362,871,667],{"class":368},[362,873,697],{"class":372},[362,875,876],{"class":368},"&",[362,878,879],{"class":866}," config",[362,881,882],{"class":384},");\n",[362,884,886,889],{"class":364,"line":885},49,[362,887,888],{"class":372},"    ~MediaRecorder",[362,890,891],{"class":384},"();\n",[362,893,895],{"class":364,"line":894},50,[362,896,441],{"emptyLinePlaceholder":440},[362,898,900,903,906],{"class":364,"line":899},51,[362,901,902],{"class":368},"    uint64_t",[362,904,905],{"class":372}," GetQPCFrequency",[362,907,891],{"class":384},[362,909,911,913,916],{"class":364,"line":910},52,[362,912,902],{"class":368},[362,914,915],{"class":372}," GetCurrentQPC",[362,917,891],{"class":384},[362,919,921],{"class":364,"line":920},53,[362,922,441],{"emptyLinePlaceholder":440},[362,924,926,929,932,934,936,938,940,942,944,947,949,951,953,955,957],{"class":364,"line":925},54,[362,927,928],{"class":372},"    HRESULT",[362,930,931],{"class":372}," StartRecording",[362,933,385],{"class":384},[362,935,667],{"class":368},[362,937,651],{"class":372},[362,939,631],{"class":384},[362,941,813],{"class":372},[362,943,876],{"class":368},[362,945,946],{"class":866}," outputPath",[362,948,391],{"class":384},[362,950,667],{"class":368},[362,952,697],{"class":372},[362,954,876],{"class":368},[362,956,879],{"class":866},[362,958,882],{"class":384},[362,960,962,964,967,969,971,973,975,977,980,982,985,988],{"class":364,"line":961},55,[362,963,928],{"class":372},[362,965,966],{"class":372}," PushVideoFrame",[362,968,385],{"class":384},[362,970,855],{"class":372},[362,972,659],{"class":384},[362,974,191],{"class":372},[362,976,863],{"class":384},[362,978,979],{"class":866},"texture",[362,981,391],{"class":384},[362,983,984],{"class":368},"uint64_t",[362,986,987],{"class":866}," qpcTime",[362,989,882],{"class":384},[362,991,993,995,998,1000,1002,1004,1007,1009,1012,1015,1017,1019,1021],{"class":364,"line":992},56,[362,994,928],{"class":372},[362,996,997],{"class":372}," PushAudioData",[362,999,385],{"class":384},[362,1001,667],{"class":368},[362,1003,670],{"class":368},[362,1005,1006],{"class":866}," data",[362,1008,391],{"class":384},[362,1010,1011],{"class":368},"size_t",[362,1013,1014],{"class":866}," size",[362,1016,391],{"class":384},[362,1018,984],{"class":368},[362,1020,987],{"class":866},[362,1022,882],{"class":384},[362,1024,1026,1029,1032],{"class":364,"line":1025},57,[362,1027,1028],{"class":368},"    void",[362,1030,1031],{"class":372}," StopRecording",[362,1033,891],{"class":384},[362,1035,1037],{"class":364,"line":1036},58,[362,1038,441],{"emptyLinePlaceholder":440},[362,1040,1042,1044,1046,1049,1051,1054,1057,1060],{"class":364,"line":1041},59,[362,1043,808],{"class":372},[362,1045,631],{"class":384},[362,1047,1048],{"class":372},"atomic",[362,1050,659],{"class":384},[362,1052,1053],{"class":368},"bool",[362,1055,1056],{"class":384},"> m_isRecording{ ",[362,1058,1059],{"class":739},"false",[362,1061,1062],{"class":384}," };\n",[362,1064,1066,1068,1071,1073,1076],{"class":364,"line":1065},60,[362,1067,902],{"class":368},[362,1069,1070],{"class":384}," m_startTimeQPC ",[362,1072,736],{"class":368},[362,1074,1075],{"class":739}," 0",[362,1077,743],{"class":384},[362,1079,1081,1083,1086,1088,1090],{"class":364,"line":1080},61,[362,1082,902],{"class":368},[362,1084,1085],{"class":384}," m_qpcFrequency ",[362,1087,736],{"class":368},[362,1089,1075],{"class":739},[362,1091,743],{"class":384},[362,1093,1095],{"class":364,"line":1094},62,[362,1096,441],{"emptyLinePlaceholder":440},[362,1098,1100],{"class":364,"line":1099},63,[362,1101,1102],{"class":368},"private:\n",[362,1104,1106,1108,1111,1113,1115,1117,1119,1121],{"class":364,"line":1105},64,[362,1107,928],{"class":372},[362,1109,1110],{"class":372}," SetupVideoStreaming",[362,1112,385],{"class":384},[362,1114,667],{"class":368},[362,1116,697],{"class":372},[362,1118,876],{"class":368},[362,1120,879],{"class":866},[362,1122,882],{"class":384},[362,1124,1126,1128,1131,1133,1135,1137,1139,1141],{"class":364,"line":1125},65,[362,1127,928],{"class":372},[362,1129,1130],{"class":372}," SetupAudioStreaming",[362,1132,385],{"class":384},[362,1134,667],{"class":368},[362,1136,697],{"class":372},[362,1138,876],{"class":368},[362,1140,879],{"class":866},[362,1142,882],{"class":384},[362,1144,1146,1148,1151,1153,1155,1157,1159,1161,1163,1165,1168,1171,1173,1175,1178],{"class":364,"line":1145},66,[362,1147,928],{"class":372},[362,1149,1150],{"class":372}," PushVideoFrame_RgbMemory",[362,1152,385],{"class":384},[362,1154,855],{"class":372},[362,1156,659],{"class":384},[362,1158,191],{"class":372},[362,1160,863],{"class":384},[362,1162,979],{"class":866},[362,1164,391],{"class":384},[362,1166,1167],{"class":372},"LONGLONG",[362,1169,1170],{"class":866}," pts",[362,1172,391],{"class":384},[362,1174,1167],{"class":372},[362,1176,1177],{"class":866}," frameDur100ns",[362,1179,882],{"class":384},[362,1181,1183],{"class":364,"line":1182},67,[362,1184,441],{"emptyLinePlaceholder":440},[362,1186,1188,1191,1193,1195],{"class":364,"line":1187},68,[362,1189,1190],{"class":372},"    ComPtr",[362,1192,659],{"class":384},[362,1194,860],{"class":372},[362,1196,1197],{"class":384},"> m_pDevice;\n",[362,1199,1201,1203,1205,1208],{"class":364,"line":1200},69,[362,1202,1190],{"class":372},[362,1204,659],{"class":384},[362,1206,1207],{"class":372},"ID3D11DeviceContext",[362,1209,1210],{"class":384},"> m_pImmediateContext;\n",[362,1212,1214,1216,1218,1220],{"class":364,"line":1213},70,[362,1215,1190],{"class":372},[362,1217,659],{"class":384},[362,1219,191],{"class":372},[362,1221,1222],{"class":384},"> m_stagingReadback;\n",[362,1224,1226,1228,1231,1233,1236],{"class":364,"line":1225},71,[362,1227,786],{"class":368},[362,1229,1230],{"class":384}," m_useRgbMemoryPath ",[362,1232,736],{"class":368},[362,1234,1235],{"class":739}," false",[362,1237,743],{"class":384},[362,1239,1241,1243,1245,1248],{"class":364,"line":1240},72,[362,1242,1190],{"class":372},[362,1244,659],{"class":384},[362,1246,1247],{"class":372},"IMFDXGIDeviceManager",[362,1249,1250],{"class":384},"> m_pDeviceManager;\n",[362,1252,1254,1256,1258,1260],{"class":364,"line":1253},73,[362,1255,1190],{"class":372},[362,1257,659],{"class":384},[362,1259,23],{"class":372},[362,1261,1262],{"class":384},"> m_sinkWriter;\n",[362,1264,1266],{"class":364,"line":1265},74,[362,1267,441],{"emptyLinePlaceholder":440},[362,1269,1271,1274],{"class":364,"line":1270},75,[362,1272,1273],{"class":372},"    RecorderConfig",[362,1275,1276],{"class":384}," m_config;\n",[362,1278,1280,1283,1286,1288,1290],{"class":364,"line":1279},76,[362,1281,1282],{"class":372},"    DWORD",[362,1284,1285],{"class":384}," m_videoStreamIndex ",[362,1287,736],{"class":368},[362,1289,1075],{"class":739},[362,1291,743],{"class":384},[362,1293,1295,1297,1300,1302,1304],{"class":364,"line":1294},77,[362,1296,1282],{"class":372},[362,1298,1299],{"class":384}," m_audioStreamIndex ",[362,1301,736],{"class":368},[362,1303,1075],{"class":739},[362,1305,743],{"class":384},[362,1307,1309,1311,1314,1316,1318],{"class":364,"line":1308},78,[362,1310,786],{"class":368},[362,1312,1313],{"class":384}," m_hasAudioStream ",[362,1315,736],{"class":368},[362,1317,1235],{"class":739},[362,1319,743],{"class":384},[362,1321,1323],{"class":364,"line":1322},79,[362,1324,822],{"class":384},[362,1326,1328],{"class":364,"line":1327},80,[362,1329,441],{"emptyLinePlaceholder":440},[362,1331,1333,1335,1338],{"class":364,"line":1332},81,[362,1334,833],{"class":368},[362,1336,1337],{"class":372}," WasapiLoopback",[362,1339,700],{"class":384},[362,1341,1343],{"class":364,"line":1342},82,[362,1344,844],{"class":368},[362,1346,1348,1351],{"class":364,"line":1347},83,[362,1349,1350],{"class":372},"    WasapiLoopback",[362,1352,891],{"class":384},[362,1354,1356,1359],{"class":364,"line":1355},84,[362,1357,1358],{"class":372},"    ~WasapiLoopback",[362,1360,891],{"class":384},[362,1362,1364],{"class":364,"line":1363},85,[362,1365,441],{"emptyLinePlaceholder":440},[362,1367,1369,1371,1373,1375,1377,1379,1382,1384,1387],{"class":364,"line":1368},86,[362,1370,1350],{"class":372},[362,1372,385],{"class":384},[362,1374,667],{"class":368},[362,1376,1337],{"class":372},[362,1378,876],{"class":368},[362,1380,1381],{"class":384},") ",[362,1383,736],{"class":368},[362,1385,1386],{"class":368}," delete",[362,1388,743],{"class":384},[362,1390,1392,1394,1396,1399,1401,1403,1405,1407,1409,1411,1413,1415],{"class":364,"line":1391},87,[362,1393,1350],{"class":372},[362,1395,876],{"class":368},[362,1397,1398],{"class":368}," operator",[362,1400,736],{"class":372},[362,1402,385],{"class":384},[362,1404,667],{"class":368},[362,1406,1337],{"class":372},[362,1408,876],{"class":368},[362,1410,1381],{"class":384},[362,1412,736],{"class":368},[362,1414,1386],{"class":368},[362,1416,743],{"class":384},[362,1418,1420],{"class":364,"line":1419},88,[362,1421,441],{"emptyLinePlaceholder":440},[362,1423,1425],{"class":364,"line":1424},89,[362,1426,1427],{"class":779},"    \u002F** deviceId 为空或 nullptr：默认渲染端点（控制台）；否则 GetDevice(deviceId) *\u002F\n",[362,1429,1431,1433,1436,1438,1441,1444,1446,1449],{"class":364,"line":1430},90,[362,1432,928],{"class":372},[362,1434,1435],{"class":372}," Initialize",[362,1437,385],{"class":384},[362,1439,1440],{"class":372},"LPCWSTR",[362,1442,1443],{"class":866}," deviceId",[362,1445,648],{"class":368},[362,1447,1448],{"class":739}," nullptr",[362,1450,882],{"class":384},[362,1452,1454],{"class":364,"line":1453},91,[362,1455,441],{"emptyLinePlaceholder":440},[362,1457,1459,1461,1464,1466,1469,1472],{"class":364,"line":1458},92,[362,1460,1028],{"class":368},[362,1462,1463],{"class":372}," Start",[362,1465,385],{"class":384},[362,1467,1468],{"class":372},"AudioDataCallback",[362,1470,1471],{"class":866}," callback",[362,1473,882],{"class":384},[362,1475,1477,1479,1482],{"class":364,"line":1476},93,[362,1478,1028],{"class":368},[362,1480,1481],{"class":372}," Stop",[362,1483,891],{"class":384},[362,1485,1487],{"class":364,"line":1486},94,[362,1488,441],{"emptyLinePlaceholder":440},[362,1490,1492,1495,1498,1501,1504,1506,1509,1512],{"class":364,"line":1491},95,[362,1493,1494],{"class":372},"    WAVEFORMATEX",[362,1496,1497],{"class":368},"*",[362,1499,1500],{"class":372}," GetFormat",[362,1502,1503],{"class":384},"() ",[362,1505,667],{"class":368},[362,1507,1508],{"class":384}," { ",[362,1510,1511],{"class":368},"return",[362,1513,1514],{"class":384}," m_pwfx; }\n",[362,1516,1518],{"class":364,"line":1517},96,[362,1519,441],{"emptyLinePlaceholder":440},[362,1521,1523],{"class":364,"line":1522},97,[362,1524,1102],{"class":368},[362,1526,1528,1530,1533],{"class":364,"line":1527},98,[362,1529,1028],{"class":368},[362,1531,1532],{"class":372}," CaptureThread",[362,1534,891],{"class":384},[362,1536,1538,1540,1542,1544],{"class":364,"line":1537},99,[362,1539,1190],{"class":372},[362,1541,659],{"class":384},[362,1543,129],{"class":372},[362,1545,1546],{"class":384},"> m_audioClient;\n",[362,1548,1550,1552,1554,1556],{"class":364,"line":1549},100,[362,1551,1190],{"class":372},[362,1553,659],{"class":384},[362,1555,133],{"class":372},[362,1557,1558],{"class":384},"> m_captureClient;\n",[362,1560,1562,1564,1566,1569,1571,1573],{"class":364,"line":1561},101,[362,1563,1494],{"class":372},[362,1565,1497],{"class":368},[362,1567,1568],{"class":384}," m_pwfx ",[362,1570,736],{"class":368},[362,1572,1448],{"class":739},[362,1574,743],{"class":384},[362,1576,1578,1581],{"class":364,"line":1577},102,[362,1579,1580],{"class":372},"    AudioDataCallback",[362,1582,1583],{"class":384}," m_onData;\n",[362,1585,1587,1589,1591,1594],{"class":364,"line":1586},103,[362,1588,808],{"class":372},[362,1590,631],{"class":384},[362,1592,1593],{"class":372},"thread",[362,1595,1596],{"class":384}," m_captureThread;\n",[362,1598,1600,1602,1604,1606,1608,1610,1613,1615],{"class":364,"line":1599},104,[362,1601,808],{"class":372},[362,1603,631],{"class":384},[362,1605,1048],{"class":372},[362,1607,659],{"class":384},[362,1609,1053],{"class":368},[362,1611,1612],{"class":384},"> m_isRunning{ ",[362,1614,1059],{"class":739},[362,1616,1062],{"class":384},[362,1618,1620,1622,1625,1627,1629],{"class":364,"line":1619},105,[362,1621,706],{"class":368},[362,1623,1624],{"class":384}," m_frameSize ",[362,1626,736],{"class":368},[362,1628,1075],{"class":739},[362,1630,743],{"class":384},[362,1632,1634],{"class":364,"line":1633},106,[362,1635,822],{"class":384},[10,1637,1638],{},[14,1639,1640],{},"分工简述",[223,1642,1643,1663],{},[226,1644,1645,1650,1651,1656,1657,1662],{},[14,1646,1647],{},[21,1648,1649],{},"WasapiLoopback","：独立线程 ",[14,1652,1653],{},[21,1654,1655],{},"GetBuffer","，把 loopback PCM 通过回调交出；",[14,1658,1659],{},[21,1660,1661],{},"qpcPosition"," 给上层对齐视频 QPC。",[226,1664,1665,1670,1671,1675,1676,1679,1680,1687,1688,252],{},[14,1666,1667],{},[21,1668,1669],{},"MediaRecorder","：创建 ",[14,1672,1673],{},[21,1674,23],{},"，配置 ",[14,1677,1678],{},"H.264 +（可选）AAC","，",[14,1681,1682,151,1685],{},[21,1683,1684],{},"PushVideoFrame",[21,1686,173],{}," 写 ",[14,1689,1690],{},[21,1691,1692],{},"IMFSample",[99,1694],{},[118,1696,1698,1701],{"id":1697},"hresult-与-com-成败判断",[21,1699,1700],{},"HRESULT"," 与 COM 成败判断",[10,1703,1704,1706,1707,1713,1714,1717,1718,1721],{},[21,1705,1700],{}," 的位域分类（严重性、Facility、Code）你笔记里表格已经够用。实务上：",[14,1708,1709,1710],{},"成功用 ",[21,1711,1712],{},"SUCCEEDED(hr)","，不要只写 ",[21,1715,1716],{},"== S_OK","，因为有些 API 会返回 ",[21,1719,1720],{},"S_FALSE"," 仍算成功语义。",[99,1723],{},[118,1725,1727,1729],{"id":1726},"wasapiloopbackinitialize设备与-loopback",[21,1728,349],{},"：设备与 LOOPBACK",[10,1731,1732,1733,1738,1739,1744,1745,1751,1752,1760],{},"旧笔记只贴了 ",[14,1734,1735],{},[21,1736,1737],{},"GetDefaultAudioEndpoint","。当前实现支持 ",[14,1740,1741],{},[21,1742,1743],{},"IMMDeviceEnumerator::GetDevice(deviceId)"," 指定播放设备；随后 ",[14,1746,1747,1748],{},"共享模式 + LOOPBACK + ",[21,1749,1750],{},"GetMixFormat","，再 ",[14,1753,1754,151,1757],{},[21,1755,1756],{},"Initialize",[21,1758,1759],{},"GetService(IAudioCaptureClient)","。片段摘自仓库（省略与正文重复的注释）：",[353,1762,1764],{"className":355,"code":1763,"language":357,"meta":358,"style":358},"HRESULT WasapiLoopback::Initialize(LPCWSTR deviceId)\n{\n    \u002F\u002F … Stop、释放旧 m_pwfx \u002F COM 接口 …\n\n    ComPtr\u003CIMMDeviceEnumerator> pEnumerator;\n    HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,\n        __uuidof(IMMDeviceEnumerator), (void**)pEnumerator.GetAddressOf());\n    if (FAILED(hr)) return hr;\n\n    ComPtr\u003CIMMDevice> pDevice;\n    if (deviceId != nullptr && deviceId[0] != L'\\0')\n        hr = pEnumerator->GetDevice(deviceId, pDevice.GetAddressOf());\n    else\n        hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDevice.GetAddressOf());\n    if (FAILED(hr)) return hr;\n\n    hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,\n        (void**)m_audioClient.GetAddressOf());\n    if (FAILED(hr)) return hr;\n\n    hr = m_audioClient->GetMixFormat(&m_pwfx);\n    if (FAILED(hr)) return hr;\n\n    constexpr REFERENCE_TIME kBufferDuration = 500 * 10000; \u002F\u002F 500ms，100ns 单位\n    hr = m_audioClient->Initialize(\n        AUDCLNT_SHAREMODE_SHARED,\n        AUDCLNT_STREAMFLAGS_LOOPBACK,\n        kBufferDuration,\n        0,\n        m_pwfx,\n        nullptr);\n    if (FAILED(hr)) return hr;\n\n    hr = m_audioClient->GetService(__uuidof(IAudioCaptureClient),\n        (void**)m_captureClient.GetAddressOf());\n    if (FAILED(hr)) return hr;\n\n    m_frameSize = m_pwfx->nBlockAlign;\n    return S_OK;\n}\n",[21,1765,1766,1784,1789,1794,1798,1810,1836,1856,1875,1879,1891,1928,1948,1953,1970,1984,1988,2013,2027,2041,2045,2063,2077,2081,2106,2119,2124,2129,2134,2141,2146,2153,2167,2171,2189,2202,2216,2220,2230,2238],{"__ignoreMap":358},[362,1767,1768,1770,1772,1774,1776,1778,1780,1782],{"class":364,"line":365},[362,1769,1700],{"class":372},[362,1771,1337],{"class":372},[362,1773,631],{"class":384},[362,1775,1756],{"class":372},[362,1777,385],{"class":384},[362,1779,1440],{"class":372},[362,1781,1443],{"class":866},[362,1783,398],{"class":384},[362,1785,1786],{"class":364,"line":376},[362,1787,1788],{"class":384},"{\n",[362,1790,1791],{"class":364,"line":401},[362,1792,1793],{"class":779},"    \u002F\u002F … Stop、释放旧 m_pwfx \u002F COM 接口 …\n",[362,1795,1796],{"class":364,"line":419},[362,1797,441],{"emptyLinePlaceholder":440},[362,1799,1800,1802,1804,1807],{"class":364,"line":437},[362,1801,1190],{"class":372},[362,1803,659],{"class":384},[362,1805,1806],{"class":372},"IMMDeviceEnumerator",[362,1808,1809],{"class":384},"> pEnumerator;\n",[362,1811,1812,1814,1817,1819,1822,1824,1827,1830,1833],{"class":364,"line":444},[362,1813,928],{"class":372},[362,1815,1816],{"class":384}," hr ",[362,1818,736],{"class":368},[362,1820,1821],{"class":372}," CoCreateInstance",[362,1823,385],{"class":384},[362,1825,1826],{"class":372},"__uuidof",[362,1828,1829],{"class":384},"(MMDeviceEnumerator), ",[362,1831,1832],{"class":739},"nullptr",[362,1834,1835],{"class":384},", CLSCTX_ALL,\n",[362,1837,1838,1841,1844,1847,1850,1853],{"class":364,"line":453},[362,1839,1840],{"class":372},"        __uuidof",[362,1842,1843],{"class":384},"(IMMDeviceEnumerator), (",[362,1845,1846],{"class":368},"void**",[362,1848,1849],{"class":384},")pEnumerator.",[362,1851,1852],{"class":372},"GetAddressOf",[362,1854,1855],{"class":384},"());\n",[362,1857,1858,1861,1864,1867,1870,1872],{"class":364,"line":461},[362,1859,1860],{"class":368},"    if",[362,1862,1863],{"class":384}," (",[362,1865,1866],{"class":372},"FAILED",[362,1868,1869],{"class":384},"(hr)) ",[362,1871,1511],{"class":368},[362,1873,1874],{"class":384}," hr;\n",[362,1876,1877],{"class":364,"line":469},[362,1878,441],{"emptyLinePlaceholder":440},[362,1880,1881,1883,1885,1888],{"class":364,"line":477},[362,1882,1190],{"class":372},[362,1884,659],{"class":384},[362,1886,1887],{"class":372},"IMMDevice",[362,1889,1890],{"class":384},"> pDevice;\n",[362,1892,1893,1895,1898,1901,1903,1906,1909,1912,1915,1917,1920,1923,1926],{"class":364,"line":482},[362,1894,1860],{"class":368},[362,1896,1897],{"class":384}," (deviceId ",[362,1899,1900],{"class":368},"!=",[362,1902,1448],{"class":739},[362,1904,1905],{"class":368}," &&",[362,1907,1908],{"class":384}," deviceId[",[362,1910,1911],{"class":739},"0",[362,1913,1914],{"class":384},"] ",[362,1916,1900],{"class":368},[362,1918,1919],{"class":394}," L'",[362,1921,1922],{"class":739},"\\0",[362,1924,1925],{"class":394},"'",[362,1927,398],{"class":384},[362,1929,1930,1933,1935,1938,1941,1944,1946],{"class":364,"line":490},[362,1931,1932],{"class":384},"        hr ",[362,1934,736],{"class":368},[362,1936,1937],{"class":384}," pEnumerator->",[362,1939,1940],{"class":372},"GetDevice",[362,1942,1943],{"class":384},"(deviceId, pDevice.",[362,1945,1852],{"class":372},[362,1947,1855],{"class":384},[362,1949,1950],{"class":364,"line":498},[362,1951,1952],{"class":368},"    else\n",[362,1954,1955,1957,1959,1961,1963,1966,1968],{"class":364,"line":506},[362,1956,1932],{"class":384},[362,1958,736],{"class":368},[362,1960,1937],{"class":384},[362,1962,1737],{"class":372},[362,1964,1965],{"class":384},"(eRender, eConsole, pDevice.",[362,1967,1852],{"class":372},[362,1969,1855],{"class":384},[362,1971,1972,1974,1976,1978,1980,1982],{"class":364,"line":514},[362,1973,1860],{"class":368},[362,1975,1863],{"class":384},[362,1977,1866],{"class":372},[362,1979,1869],{"class":384},[362,1981,1511],{"class":368},[362,1983,1874],{"class":384},[362,1985,1986],{"class":364,"line":522},[362,1987,441],{"emptyLinePlaceholder":440},[362,1989,1990,1993,1995,1998,2001,2003,2005,2008,2010],{"class":364,"line":527},[362,1991,1992],{"class":384},"    hr ",[362,1994,736],{"class":368},[362,1996,1997],{"class":384}," pDevice->",[362,1999,2000],{"class":372},"Activate",[362,2002,385],{"class":384},[362,2004,1826],{"class":372},[362,2006,2007],{"class":384},"(IAudioClient), CLSCTX_ALL, ",[362,2009,1832],{"class":739},[362,2011,2012],{"class":384},",\n",[362,2014,2015,2018,2020,2023,2025],{"class":364,"line":535},[362,2016,2017],{"class":384},"        (",[362,2019,1846],{"class":368},[362,2021,2022],{"class":384},")m_audioClient.",[362,2024,1852],{"class":372},[362,2026,1855],{"class":384},[362,2028,2029,2031,2033,2035,2037,2039],{"class":364,"line":543},[362,2030,1860],{"class":368},[362,2032,1863],{"class":384},[362,2034,1866],{"class":372},[362,2036,1869],{"class":384},[362,2038,1511],{"class":368},[362,2040,1874],{"class":384},[362,2042,2043],{"class":364,"line":551},[362,2044,441],{"emptyLinePlaceholder":440},[362,2046,2047,2049,2051,2054,2056,2058,2060],{"class":364,"line":556},[362,2048,1992],{"class":384},[362,2050,736],{"class":368},[362,2052,2053],{"class":384}," m_audioClient->",[362,2055,1750],{"class":372},[362,2057,385],{"class":384},[362,2059,876],{"class":368},[362,2061,2062],{"class":384},"m_pwfx);\n",[362,2064,2065,2067,2069,2071,2073,2075],{"class":364,"line":564},[362,2066,1860],{"class":368},[362,2068,1863],{"class":384},[362,2070,1866],{"class":372},[362,2072,1869],{"class":384},[362,2074,1511],{"class":368},[362,2076,1874],{"class":384},[362,2078,2079],{"class":364,"line":569},[362,2080,441],{"emptyLinePlaceholder":440},[362,2082,2083,2086,2089,2091,2094,2097,2100,2103],{"class":364,"line":577},[362,2084,2085],{"class":368},"    constexpr",[362,2087,2088],{"class":384}," REFERENCE_TIME kBufferDuration ",[362,2090,736],{"class":368},[362,2092,2093],{"class":739}," 500",[362,2095,2096],{"class":368}," *",[362,2098,2099],{"class":739}," 10000",[362,2101,2102],{"class":384},";",[362,2104,2105],{"class":779}," \u002F\u002F 500ms，100ns 单位\n",[362,2107,2108,2110,2112,2114,2116],{"class":364,"line":585},[362,2109,1992],{"class":384},[362,2111,736],{"class":368},[362,2113,2053],{"class":384},[362,2115,1756],{"class":372},[362,2117,2118],{"class":384},"(\n",[362,2120,2121],{"class":364,"line":593},[362,2122,2123],{"class":384},"        AUDCLNT_SHAREMODE_SHARED,\n",[362,2125,2126],{"class":364,"line":601},[362,2127,2128],{"class":384},"        AUDCLNT_STREAMFLAGS_LOOPBACK,\n",[362,2130,2131],{"class":364,"line":609},[362,2132,2133],{"class":384},"        kBufferDuration,\n",[362,2135,2136,2139],{"class":364,"line":617},[362,2137,2138],{"class":739},"        0",[362,2140,2012],{"class":384},[362,2142,2143],{"class":364,"line":622},[362,2144,2145],{"class":384},"        m_pwfx,\n",[362,2147,2148,2151],{"class":364,"line":640},[362,2149,2150],{"class":739},"        nullptr",[362,2152,882],{"class":384},[362,2154,2155,2157,2159,2161,2163,2165],{"class":364,"line":686},[362,2156,1860],{"class":368},[362,2158,1863],{"class":384},[362,2160,1866],{"class":372},[362,2162,1869],{"class":384},[362,2164,1511],{"class":368},[362,2166,1874],{"class":384},[362,2168,2169],{"class":364,"line":691},[362,2170,441],{"emptyLinePlaceholder":440},[362,2172,2173,2175,2177,2179,2182,2184,2186],{"class":364,"line":703},[362,2174,1992],{"class":384},[362,2176,736],{"class":368},[362,2178,2053],{"class":384},[362,2180,2181],{"class":372},"GetService",[362,2183,385],{"class":384},[362,2185,1826],{"class":372},[362,2187,2188],{"class":384},"(IAudioCaptureClient),\n",[362,2190,2191,2193,2195,2198,2200],{"class":364,"line":712},[362,2192,2017],{"class":384},[362,2194,1846],{"class":368},[362,2196,2197],{"class":384},")m_captureClient.",[362,2199,1852],{"class":372},[362,2201,1855],{"class":384},[362,2203,2204,2206,2208,2210,2212,2214],{"class":364,"line":720},[362,2205,1860],{"class":368},[362,2207,1863],{"class":384},[362,2209,1866],{"class":372},[362,2211,1869],{"class":384},[362,2213,1511],{"class":368},[362,2215,1874],{"class":384},[362,2217,2218],{"class":364,"line":728},[362,2219,441],{"emptyLinePlaceholder":440},[362,2221,2222,2225,2227],{"class":364,"line":746},[362,2223,2224],{"class":384},"    m_frameSize ",[362,2226,736],{"class":368},[362,2228,2229],{"class":384}," m_pwfx->nBlockAlign;\n",[362,2231,2232,2235],{"class":364,"line":761},[362,2233,2234],{"class":368},"    return",[362,2236,2237],{"class":384}," S_OK;\n",[362,2239,2240],{"class":364,"line":776},[362,2241,2242],{"class":384},"}\n",[10,2244,2245,2246,2249,2250,2255],{},"要点：",[14,2247,2248],{},"LOOPBACK 必须配合共享模式 Initialize","；缓冲时长用 ",[14,2251,2252],{},[21,2253,2254],{},"REFERENCE_TIME","（100ns），不要和毫秒混着拍脑袋乘错数量级。",[99,2257],{},[118,2259,2261,2262,39,2265,2268],{"id":2260},"捕获线程avsetmmthreadcharacteristicswsilent-位","捕获线程：",[21,2263,2264],{},"AvSetMmThreadCharacteristicsW",[21,2266,2267],{},"SILENT"," 位",[10,2270,2271,2272,2277,2278,252],{},"采集线程里 ",[14,2273,2274],{},[21,2275,2276],{},"CoInitializeEx(..., COINIT_MULTITHREADED)"," 仍然必要——",[14,2279,2280,2282],{},[21,2281,129],{}," 指针从别的线程传来，本线程也要 COM 入场",[10,2284,2285,2286,2291],{},"MMCSS：工程里用的是 ",[14,2287,2288],{},[21,2289,2290],{},"AvSetMmThreadCharacteristicsW(L\"Pro Audio\", ...)","（宽字符串），降低被普通线程饿死导致断续的概率。",[10,2293,2294,2297,2298,2303],{},[14,2295,2296],{},"双层循环","（外层生命周期 + 内层 ",[14,2299,2300],{},[21,2301,2302],{},"GetNextPacketSize"," 清空积压）保留你原来的结论：高负载下只读一包就睡，容易堆满驱动缓冲。",[10,2305,2306,2311,2312,2318,2319,2324,2325,2330],{},[14,2307,2308],{},[21,2309,2310],{},"AUDCLNT_BUFFERFLAGS_SILENT","：静音包 ",[14,2313,2314,2317],{},[21,2315,2316],{},"pData"," 内容不可信","，仓库里在回调前对 ",[14,2320,2321],{},[21,2322,2323],{},"sizeInBytes"," 范围 ",[14,2326,2327],{},[21,2328,2329],{},"memset(0)","，再交给上层，避免把垃圾当信号：",[353,2332,2334],{"className":355,"code":2333,"language":357,"meta":358,"style":358},"if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) && pData && sizeInBytes > 0)\n    std::memset(pData, 0, sizeInBytes);\nm_onData(pData, &sizeInBytes, &qpcPosition);\n",[21,2335,2336,2367,2384],{"__ignoreMap":358},[362,2337,2338,2341,2344,2346,2349,2352,2355,2357,2360,2363,2365],{"class":364,"line":365},[362,2339,2340],{"class":368},"if",[362,2342,2343],{"class":384}," ((flags ",[362,2345,876],{"class":368},[362,2347,2348],{"class":384}," AUDCLNT_BUFFERFLAGS_SILENT) ",[362,2350,2351],{"class":368},"&&",[362,2353,2354],{"class":384}," pData ",[362,2356,2351],{"class":368},[362,2358,2359],{"class":384}," sizeInBytes ",[362,2361,2362],{"class":368},">",[362,2364,1075],{"class":739},[362,2366,398],{"class":384},[362,2368,2369,2371,2373,2376,2379,2381],{"class":364,"line":376},[362,2370,808],{"class":372},[362,2372,631],{"class":384},[362,2374,2375],{"class":372},"memset",[362,2377,2378],{"class":384},"(pData, ",[362,2380,1911],{"class":739},[362,2382,2383],{"class":384},", sizeInBytes);\n",[362,2385,2386,2389,2391,2393,2396,2398],{"class":364,"line":401},[362,2387,2388],{"class":372},"m_onData",[362,2390,2378],{"class":384},[362,2392,876],{"class":368},[362,2394,2395],{"class":384},"sizeInBytes, ",[362,2397,876],{"class":368},[362,2399,2400],{"class":384},"qpcPosition);\n",[10,2402,2403,2404,2410],{},"析构时释放 ",[14,2405,2406,151,2408],{},[21,2407,129],{},[21,2409,133],{},"，避免线程结束仍占设备。",[99,2412],{},[102,2414,2416,2418],{"id":2415},"mediarecorder两层硬件不行就软件和仓库完全一致",[21,2417,1669],{},"：两层「硬件不行就软件」——和仓库完全一致",[10,2420,2421,2422,2425,2426,55,2430,2434],{},"下面这件事一定要分清，否则你会以为「关了硬件变换 = 不用显卡」。实际上我们做了 ",[14,2423,2424],{},"两层回退","，对应 ",[14,2427,2428],{},[21,2429,54],{},[14,2431,2432],{},[21,2433,1669],{}," 的真实代码。",[118,2436,2438,2439],{"id":2437},"第一层编码拓扑mf_readwrite_enable_hardware_transforms","第一层（编码拓扑）：",[21,2440,48],{},[10,2442,2443,2446,2447,2451,2452,2454,2455,2458,2459,2462,2463,2471,2472,2477],{},[14,2444,2445],{},"含义","：告诉 ",[14,2448,2449],{},[21,2450,23],{},"：组装 ",[14,2453,205],{}," 的 MFT 链时，",[14,2456,2457],{},"不要优先选用显卡驱动注册的硬件编码器","（NVENC、Intel QSV、AMD AMF 等在 MF 里暴露的 ",[14,2460,2461],{},"硬件 MFT","）。部分驱动版本下硬件路径会在 ",[14,2464,2465,151,2468],{},[21,2466,2467],{},"BeginWriting",[21,2469,2470],{},"WriteSample"," 阶段给出 ",[14,2473,2474],{},[21,2475,2476],{},"E_INVALIDARG","、或其它 HRESULT，非常难在用户机器上复现排查。",[10,2479,2480,2481,55,2486,2489,2490,2495,2496,2499],{},"我们在 ",[14,2482,2483],{},[21,2484,2485],{},"StartRecording",[14,2487,2488],{},"静态地"," 设为 ",[14,2491,2492],{},[21,2493,2494],{},"FALSE","，等价于 ",[14,2497,2498],{},"默认走系统自带的软件编码管线","（仍是 MF，只是编码器实例多为 CPU）。源码注释写得很直白：",[353,2501,2505],{"className":2502,"code":2503,"language":2504,"meta":358,"style":358},"language-449:465:glfwExample\u002FWasapiLoopback.cpp shiki shiki-themes github-light github-dark","    \u002F\u002F 硬件编码器\u002FDXGI RGB 路径在部分机型上 WriteSample 报 E_INVALIDARG；先用软件管线保证可写\n    hr = pAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, FALSE);\n    \u002F\u002F ...\n    hr = pAttributes->SetUnknown(MF_SINK_WRITER_D3D_MANAGER, m_pDeviceManager.Get());\n    \u002F\u002F ...\n    hr = pAttributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, true);\n    \u002F\u002F ...\n    hr = MFCreateSinkWriterFromURL(path.c_str(), nullptr, pAttributes.Get(), &m_sinkWriter);\n","449:465:glfwExample\u002FWasapiLoopback.cpp",[21,2506,2507,2512,2517,2522,2527,2531,2536,2540],{"__ignoreMap":358},[362,2508,2509],{"class":364,"line":365},[362,2510,2511],{},"    \u002F\u002F 硬件编码器\u002FDXGI RGB 路径在部分机型上 WriteSample 报 E_INVALIDARG；先用软件管线保证可写\n",[362,2513,2514],{"class":364,"line":376},[362,2515,2516],{},"    hr = pAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, FALSE);\n",[362,2518,2519],{"class":364,"line":401},[362,2520,2521],{},"    \u002F\u002F ...\n",[362,2523,2524],{"class":364,"line":419},[362,2525,2526],{},"    hr = pAttributes->SetUnknown(MF_SINK_WRITER_D3D_MANAGER, m_pDeviceManager.Get());\n",[362,2528,2529],{"class":364,"line":437},[362,2530,2521],{},[362,2532,2533],{"class":364,"line":444},[362,2534,2535],{},"    hr = pAttributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, true);\n",[362,2537,2538],{"class":364,"line":453},[362,2539,2521],{},[362,2541,2542],{"class":364,"line":461},[362,2543,2544],{},"    hr = MFCreateSinkWriterFromURL(path.c_str(), nullptr, pAttributes.Get(), &m_sinkWriter);\n",[10,2546,2547,2552,2553,2556,2557,2561,2562,2566,2567,2572],{},[14,2548,2549],{},[21,2550,2551],{},"MF_SINK_WRITER_D3D_MANAGER"," 仍然要设：它提供 ",[14,2554,2555],{},"DXGI 设备管理器","，后面 ",[14,2558,2559],{},[21,2560,197],{}," 把 ",[14,2563,2564],{},[21,2565,191],{}," 交给 SinkWriter 时要用——这和「视频编码在 GPU 还是 CPU」不是同一个开关。",[14,2568,2569],{},[21,2570,2571],{},"MF_SINK_WRITER_DISABLE_THROTTLING"," 用来减轻 SinkWriter 人为拖慢写入（按需保留）。",[10,2574,2575,2578,2579,2584,2585,2590,2591,2595,2596,2600,2601,2604,2605,2609,2610,2613,2614,2617,2618,252],{},[14,2576,2577],{},"若你以后要做「先试硬件再降级」","（笔记级思路，仓库当前未写自动重试）：可在 ",[14,2580,2581],{},[21,2582,2583],{},"MFCreateSinkWriterFromURL"," 之前 ",[14,2586,2587],{},[21,2588,2589],{},"SetUINT32(..., TRUE)","，若 ",[14,2592,2593],{},[21,2594,2467],{}," 或前几帧 ",[14,2597,2598],{},[21,2599,2470],{}," 连续失败，",[14,2602,2603],{},"释放 SinkWriter","，再用 ",[14,2606,2607],{},[21,2608,2494],{}," 重建一次。注意：",[14,2611,2612],{},"同一 MP4 会话中途不能魔法切换拓扑","，通常是 ",[14,2615,2616],{},"整段录制重新开始"," 或 ",[14,2619,2620],{},"接受默认软件编码",[99,2622],{},[118,2624,2626],{"id":2625},"第二层视频输入路径dxgi-surface-失败则-rgb-内存拷贝","第二层（视频输入路径）：DXGI Surface → 失败则 RGB 内存拷贝",[10,2628,2629,2630,2633,2634,2637,2638,2646,2647,2650,2651,2656,2657,2662,2663,2679,2680,2683],{},"即使编码侧已是软件 H.264，",[14,2631,2632],{},"喂给编码器的像素","仍可优先走 ",[14,2635,2636],{},"GPU 侧的 DXGI Surface buffer","（少一次大规模 CPU Map）。若驱动\u002FMF 组合不接受当前 DXGI 封装，或 ",[14,2639,2640,2642,2643],{},[21,2641,2470],{}," 对 DXGI buffer 返回 ",[21,2644,2645],{},"E_INVALIDARG (0x80070057)","，则 ",[14,2648,2649],{},"运行时"," 置 ",[14,2652,2653],{},[21,2654,2655],{},"m_useRgbMemoryPath = true","，之后每帧走 ",[14,2658,2659],{},[21,2660,2661],{},"PushVideoFrame_RgbMemory","：",[14,2664,2665,2668,2669,2672,2673,2668,2676],{},[21,2666,2667],{},"CopyResource"," → ",[21,2670,2671],{},"Map"," staging → ",[21,2674,2675],{},"memcpy",[21,2677,2678],{},"MFCreateMemoryBuffer","。这是 ",[14,2681,2682],{},"Pixel Delivery 回退","，CPU 带宽会涨，但能救活一大批「显卡正常但 MF 挑食」的机器。",[10,2685,2686,2687,2691,2692,351],{},"仓库里的 ",[14,2688,2689],{},[21,2690,1684],{},"（节选，与行号对应 ",[14,2693,2694],{},[21,2695,54],{},[353,2697,2699],{"className":355,"code":2698,"language":357,"meta":358,"style":358},"HRESULT MediaRecorder::PushVideoFrame(ComPtr\u003CID3D11Texture2D> texture, uint64_t qpcTime) {\n    if (!m_isRecording || !m_sinkWriter) return E_FAIL;\n\n    D3D11_TEXTURE2D_DESC td{};\n    texture->GetDesc(&td);\n    if (td.Width != m_config.width || td.Height != m_config.height) {\n        \u002F\u002F … 打日志并返回 MF_E_INVALIDMEDIATYPE …\n    }\n\n    if (m_startTimeQPC == 0)\n        m_startTimeQPC = qpcTime;\n\n    const LONGLONG pts = (qpcTime - m_startTimeQPC) * 10000000 \u002F static_cast\u003CLONGLONG>(m_qpcFrequency);\n    const UINT32 fps = (m_config.fps \u003C 1u) ? 30u : m_config.fps;\n    const LONGLONG frameDur100ns =\n        (10000000LL + static_cast\u003CLONGLONG>(fps) \u002F 2LL) \u002F static_cast\u003CLONGLONG>(fps);\n\n    if (m_useRgbMemoryPath)\n        return PushVideoFrame_RgbMemory(texture, pts, frameDur100ns);\n\n    ComPtr\u003CIMFMediaBuffer> pBuffer;\n    HRESULT hr = MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), texture.Get(), 0, TRUE, &pBuffer);\n    if (FAILED(hr)) {\n        if (m_stagingReadback) {\n            m_useRgbMemoryPath = true;\n            return PushVideoFrame_RgbMemory(texture, pts, frameDur100ns);\n        }\n        return hr;\n    }\n    \u002F\u002F … MFCreateSample、SetSampleTime\u002FDuration …\n    hr = m_sinkWriter->WriteSample(m_videoStreamIndex, pSample.Get());\n    if (FAILED(hr)) {\n        constexpr HRESULT kInvalidArg = static_cast\u003CHRESULT>(0x80070057);\n        if (hr == kInvalidArg && m_stagingReadback) {\n            m_useRgbMemoryPath = true;\n            return PushVideoFrame_RgbMemory(texture, pts, frameDur100ns);\n        }\n    }\n    return hr;\n}\n",[21,2700,2701,2732,2758,2762,2770,2785,2807,2812,2817,2821,2835,2845,2849,2888,2927,2939,2981,2985,2992,3002,3006,3018,3052,3063,3071,3082,3091,3096,3102,3106,3111,3129,3139,3165,3182,3192,3200,3204,3208,3214],{"__ignoreMap":358},[362,2702,2703,2705,2707,2709,2711,2713,2715,2717,2719,2721,2723,2725,2727,2729],{"class":364,"line":365},[362,2704,1700],{"class":372},[362,2706,836],{"class":372},[362,2708,631],{"class":384},[362,2710,1684],{"class":372},[362,2712,385],{"class":384},[362,2714,855],{"class":372},[362,2716,659],{"class":384},[362,2718,191],{"class":372},[362,2720,863],{"class":384},[362,2722,979],{"class":866},[362,2724,391],{"class":384},[362,2726,984],{"class":368},[362,2728,987],{"class":866},[362,2730,2731],{"class":384},") {\n",[362,2733,2734,2736,2738,2741,2744,2747,2750,2753,2755],{"class":364,"line":376},[362,2735,1860],{"class":368},[362,2737,1863],{"class":384},[362,2739,2740],{"class":368},"!",[362,2742,2743],{"class":384},"m_isRecording ",[362,2745,2746],{"class":368},"||",[362,2748,2749],{"class":368}," !",[362,2751,2752],{"class":384},"m_sinkWriter) ",[362,2754,1511],{"class":368},[362,2756,2757],{"class":384}," E_FAIL;\n",[362,2759,2760],{"class":364,"line":401},[362,2761,441],{"emptyLinePlaceholder":440},[362,2763,2764,2767],{"class":364,"line":419},[362,2765,2766],{"class":372},"    D3D11_TEXTURE2D_DESC",[362,2768,2769],{"class":384}," td{};\n",[362,2771,2772,2775,2778,2780,2782],{"class":364,"line":437},[362,2773,2774],{"class":384},"    texture->",[362,2776,2777],{"class":372},"GetDesc",[362,2779,385],{"class":384},[362,2781,876],{"class":368},[362,2783,2784],{"class":384},"td);\n",[362,2786,2787,2789,2792,2794,2797,2799,2802,2804],{"class":364,"line":444},[362,2788,1860],{"class":368},[362,2790,2791],{"class":384}," (td.Width ",[362,2793,1900],{"class":368},[362,2795,2796],{"class":384}," m_config.width ",[362,2798,2746],{"class":368},[362,2800,2801],{"class":384}," td.Height ",[362,2803,1900],{"class":368},[362,2805,2806],{"class":384}," m_config.height) {\n",[362,2808,2809],{"class":364,"line":453},[362,2810,2811],{"class":779},"        \u002F\u002F … 打日志并返回 MF_E_INVALIDMEDIATYPE …\n",[362,2813,2814],{"class":364,"line":461},[362,2815,2816],{"class":384},"    }\n",[362,2818,2819],{"class":364,"line":469},[362,2820,441],{"emptyLinePlaceholder":440},[362,2822,2823,2825,2828,2831,2833],{"class":364,"line":477},[362,2824,1860],{"class":368},[362,2826,2827],{"class":384}," (m_startTimeQPC ",[362,2829,2830],{"class":368},"==",[362,2832,1075],{"class":739},[362,2834,398],{"class":384},[362,2836,2837,2840,2842],{"class":364,"line":482},[362,2838,2839],{"class":384},"        m_startTimeQPC ",[362,2841,736],{"class":368},[362,2843,2844],{"class":384}," qpcTime;\n",[362,2846,2847],{"class":364,"line":490},[362,2848,441],{"emptyLinePlaceholder":440},[362,2850,2851,2854,2857,2860,2862,2865,2868,2871,2873,2875,2878,2881,2883,2885],{"class":364,"line":498},[362,2852,2853],{"class":368},"    const",[362,2855,2856],{"class":372}," LONGLONG",[362,2858,2859],{"class":384}," pts ",[362,2861,736],{"class":368},[362,2863,2864],{"class":384}," (qpcTime ",[362,2866,2867],{"class":368},"-",[362,2869,2870],{"class":384}," m_startTimeQPC) ",[362,2872,1497],{"class":368},[362,2874,740],{"class":739},[362,2876,2877],{"class":368}," \u002F",[362,2879,2880],{"class":368}," static_cast\u003C",[362,2882,1167],{"class":384},[362,2884,2362],{"class":368},[362,2886,2887],{"class":384},"(m_qpcFrequency);\n",[362,2889,2890,2892,2895,2898,2900,2903,2905,2908,2911,2913,2916,2919,2921,2924],{"class":364,"line":506},[362,2891,2853],{"class":368},[362,2893,2894],{"class":372}," UINT32",[362,2896,2897],{"class":384}," fps ",[362,2899,736],{"class":368},[362,2901,2902],{"class":384}," (m_config.fps ",[362,2904,659],{"class":368},[362,2906,2907],{"class":739}," 1",[362,2909,2910],{"class":368},"u",[362,2912,1381],{"class":384},[362,2914,2915],{"class":368},"?",[362,2917,2918],{"class":739}," 30",[362,2920,2910],{"class":368},[362,2922,2923],{"class":368}," :",[362,2925,2926],{"class":384}," m_config.fps;\n",[362,2928,2929,2931,2933,2936],{"class":364,"line":514},[362,2930,2853],{"class":368},[362,2932,2856],{"class":372},[362,2934,2935],{"class":384}," frameDur100ns ",[362,2937,2938],{"class":368},"=\n",[362,2940,2941,2943,2946,2949,2952,2954,2956,2958,2961,2964,2966,2968,2970,2972,2974,2976,2978],{"class":364,"line":522},[362,2942,2017],{"class":384},[362,2944,2945],{"class":739},"10000000",[362,2947,2948],{"class":368},"LL",[362,2950,2951],{"class":368}," +",[362,2953,2880],{"class":368},[362,2955,1167],{"class":384},[362,2957,2362],{"class":368},[362,2959,2960],{"class":384},"(fps) ",[362,2962,2963],{"class":368},"\u002F",[362,2965,771],{"class":739},[362,2967,2948],{"class":368},[362,2969,1381],{"class":384},[362,2971,2963],{"class":368},[362,2973,2880],{"class":368},[362,2975,1167],{"class":384},[362,2977,2362],{"class":368},[362,2979,2980],{"class":384},"(fps);\n",[362,2982,2983],{"class":364,"line":527},[362,2984,441],{"emptyLinePlaceholder":440},[362,2986,2987,2989],{"class":364,"line":535},[362,2988,1860],{"class":368},[362,2990,2991],{"class":384}," (m_useRgbMemoryPath)\n",[362,2993,2994,2997,2999],{"class":364,"line":543},[362,2995,2996],{"class":368},"        return",[362,2998,1150],{"class":372},[362,3000,3001],{"class":384},"(texture, pts, frameDur100ns);\n",[362,3003,3004],{"class":364,"line":551},[362,3005,441],{"emptyLinePlaceholder":440},[362,3007,3008,3010,3012,3015],{"class":364,"line":556},[362,3009,1190],{"class":372},[362,3011,659],{"class":384},[362,3013,3014],{"class":372},"IMFMediaBuffer",[362,3016,3017],{"class":384},"> pBuffer;\n",[362,3019,3020,3022,3024,3026,3029,3031,3033,3036,3039,3042,3044,3047,3049],{"class":364,"line":564},[362,3021,928],{"class":372},[362,3023,1816],{"class":384},[362,3025,736],{"class":368},[362,3027,3028],{"class":372}," MFCreateDXGISurfaceBuffer",[362,3030,385],{"class":384},[362,3032,1826],{"class":372},[362,3034,3035],{"class":384},"(ID3D11Texture2D), texture.",[362,3037,3038],{"class":372},"Get",[362,3040,3041],{"class":384},"(), ",[362,3043,1911],{"class":739},[362,3045,3046],{"class":384},", TRUE, ",[362,3048,876],{"class":368},[362,3050,3051],{"class":384},"pBuffer);\n",[362,3053,3054,3056,3058,3060],{"class":364,"line":569},[362,3055,1860],{"class":368},[362,3057,1863],{"class":384},[362,3059,1866],{"class":372},[362,3061,3062],{"class":384},"(hr)) {\n",[362,3064,3065,3068],{"class":364,"line":577},[362,3066,3067],{"class":368},"        if",[362,3069,3070],{"class":384}," (m_stagingReadback) {\n",[362,3072,3073,3076,3078,3080],{"class":364,"line":585},[362,3074,3075],{"class":384},"            m_useRgbMemoryPath ",[362,3077,736],{"class":368},[362,3079,794],{"class":739},[362,3081,743],{"class":384},[362,3083,3084,3087,3089],{"class":364,"line":593},[362,3085,3086],{"class":368},"            return",[362,3088,1150],{"class":372},[362,3090,3001],{"class":384},[362,3092,3093],{"class":364,"line":601},[362,3094,3095],{"class":384},"        }\n",[362,3097,3098,3100],{"class":364,"line":609},[362,3099,2996],{"class":368},[362,3101,1874],{"class":384},[362,3103,3104],{"class":364,"line":617},[362,3105,2816],{"class":384},[362,3107,3108],{"class":364,"line":622},[362,3109,3110],{"class":779},"    \u002F\u002F … MFCreateSample、SetSampleTime\u002FDuration …\n",[362,3112,3113,3115,3117,3120,3122,3125,3127],{"class":364,"line":640},[362,3114,1992],{"class":384},[362,3116,736],{"class":368},[362,3118,3119],{"class":384}," m_sinkWriter->",[362,3121,2470],{"class":372},[362,3123,3124],{"class":384},"(m_videoStreamIndex, pSample.",[362,3126,3038],{"class":372},[362,3128,1855],{"class":384},[362,3130,3131,3133,3135,3137],{"class":364,"line":686},[362,3132,1860],{"class":368},[362,3134,1863],{"class":384},[362,3136,1866],{"class":372},[362,3138,3062],{"class":384},[362,3140,3141,3144,3147,3149,3151,3153,3155,3157,3160,3163],{"class":364,"line":691},[362,3142,3143],{"class":368},"        constexpr",[362,3145,3146],{"class":384}," HRESULT kInvalidArg ",[362,3148,736],{"class":368},[362,3150,2880],{"class":368},[362,3152,1700],{"class":384},[362,3154,2362],{"class":368},[362,3156,385],{"class":384},[362,3158,3159],{"class":368},"0x",[362,3161,3162],{"class":739},"80070057",[362,3164,882],{"class":384},[362,3166,3167,3169,3172,3174,3177,3179],{"class":364,"line":703},[362,3168,3067],{"class":368},[362,3170,3171],{"class":384}," (hr ",[362,3173,2830],{"class":368},[362,3175,3176],{"class":384}," kInvalidArg ",[362,3178,2351],{"class":368},[362,3180,3181],{"class":384}," m_stagingReadback) {\n",[362,3183,3184,3186,3188,3190],{"class":364,"line":712},[362,3185,3075],{"class":384},[362,3187,736],{"class":368},[362,3189,794],{"class":739},[362,3191,743],{"class":384},[362,3193,3194,3196,3198],{"class":364,"line":720},[362,3195,3086],{"class":368},[362,3197,1150],{"class":372},[362,3199,3001],{"class":384},[362,3201,3202],{"class":364,"line":728},[362,3203,3095],{"class":384},[362,3205,3206],{"class":364,"line":746},[362,3207,2816],{"class":384},[362,3209,3210,3212],{"class":364,"line":761},[362,3211,2234],{"class":368},[362,3213,1874],{"class":384},[362,3215,3216],{"class":364,"line":776},[362,3217,2242],{"class":384},[10,3219,3220],{},"当硬件编码器不可靠的时候，可能是驱动不兼容，可能是格式限制，导致无法直接处理显存纹理的时候，我们就用不了零拷贝了，这个时候我们会选择copy回cpu来处理：",[353,3222,3224],{"className":355,"code":3223,"language":357,"meta":358,"style":358},"HRESULT hr = m_pImmediateContext->Map(m_stagingReadback.Get(), 0, D3D11_MAP_READ, 0, &map);\n",[21,3225,3226],{"__ignoreMap":358},[362,3227,3228,3231,3233,3236,3238,3241,3243,3245,3247,3250,3252,3254,3256],{"class":364,"line":365},[362,3229,3230],{"class":384},"HRESULT hr ",[362,3232,736],{"class":368},[362,3234,3235],{"class":384}," m_pImmediateContext->",[362,3237,2671],{"class":372},[362,3239,3240],{"class":384},"(m_stagingReadback.",[362,3242,3038],{"class":372},[362,3244,3041],{"class":384},[362,3246,1911],{"class":739},[362,3248,3249],{"class":384},", D3D11_MAP_READ, ",[362,3251,1911],{"class":739},[362,3253,391],{"class":384},[362,3255,876],{"class":368},[362,3257,3258],{"class":384},"map);\n",[10,3260,3261],{},"m_stagingReadback是我们指定的资源，这里使我们之前创建的暂存纹理，参数0用于指定资源内部索引，对于我们录屏使用的2D纹理，它索引为1。D3D11_MAP_RAED用于告知怎么操作这块内存，我们使用这个值告诉GPU ：cpu需要读取，gpu会确保数据同步到缓存里，供我们读取。 其他还有几个标志：",[223,3263,3264,3272,3280],{},[226,3265,3266,3271],{},[14,3267,3268],{},[21,3269,3270],{},"D3D11_MAP_WRITE","：我要改数据。",[226,3273,3274,3279],{},[14,3275,3276],{},[21,3277,3278],{},"D3D11_MAP_READ_WRITE","：又读又改（性能最差）。",[226,3281,3282,3287],{},[14,3283,3284],{},[21,3285,3286],{},"D3D11_MAP_WRITE_DISCARD","：我要写新的，旧的直接扔掉（这是上传顶点数据到 GPU 时常用的，非常快）。",[353,3289,3291],{"className":355,"code":3290,"language":357,"meta":358,"style":358},"    const UINT packedPitch = m_config.width * 4u;\n    const DWORD packedSize = packedPitch * m_config.height;\n",[21,3292,3293,3313],{"__ignoreMap":358},[362,3294,3295,3297,3300,3302,3304,3306,3309,3311],{"class":364,"line":365},[362,3296,2853],{"class":368},[362,3298,3299],{"class":384}," UINT packedPitch ",[362,3301,736],{"class":368},[362,3303,2796],{"class":384},[362,3305,1497],{"class":368},[362,3307,3308],{"class":739}," 4",[362,3310,2910],{"class":368},[362,3312,743],{"class":384},[362,3314,3315,3317,3320,3322,3325,3327],{"class":364,"line":376},[362,3316,2853],{"class":368},[362,3318,3319],{"class":384}," DWORD packedSize ",[362,3321,736],{"class":368},[362,3323,3324],{"class":384}," packedPitch ",[362,3326,1497],{"class":368},[362,3328,3329],{"class":384}," m_config.height;\n",[10,3331,3332],{},"这个是图像的内存布局，是我们将数据从显卡的对其空间压缩到媒体编码器的紧凑控件的计算。第一行代码用于计算图像的一行的有效数据长度，由于一个像素包含4个通道，一个通道占用1个字节，因此乘以4。第二行是计算整帧的图像总字节数， 每一行的字节数 × 总行数（高度）。",[10,3334,3335],{},"这个值会用于创建MF的内存Buffer，来存储图像。",[353,3337,3339],{"className":355,"code":3338,"language":357,"meta":358,"style":358},"    ComPtr\u003CIMFMediaBuffer> buf;\n    hr = MFCreateMemoryBuffer(packedSize, &buf);\n    if (FAILED(hr)) {\n        m_pImmediateContext->Unmap(m_stagingReadback.Get(), 0);\n        return hr;\n    }\n",[21,3340,3341,3354,3371,3381,3399,3405],{"__ignoreMap":358},[362,3342,3343,3345,3347,3349,3351],{"class":364,"line":365},[362,3344,1190],{"class":384},[362,3346,659],{"class":368},[362,3348,3014],{"class":384},[362,3350,2362],{"class":368},[362,3352,3353],{"class":384}," buf;\n",[362,3355,3356,3358,3360,3363,3366,3368],{"class":364,"line":376},[362,3357,1992],{"class":384},[362,3359,736],{"class":368},[362,3361,3362],{"class":372}," MFCreateMemoryBuffer",[362,3364,3365],{"class":384},"(packedSize, ",[362,3367,876],{"class":368},[362,3369,3370],{"class":384},"buf);\n",[362,3372,3373,3375,3377,3379],{"class":364,"line":401},[362,3374,1860],{"class":368},[362,3376,1863],{"class":384},[362,3378,1866],{"class":372},[362,3380,3062],{"class":384},[362,3382,3383,3386,3389,3391,3393,3395,3397],{"class":364,"line":419},[362,3384,3385],{"class":384},"        m_pImmediateContext->",[362,3387,3388],{"class":372},"Unmap",[362,3390,3240],{"class":384},[362,3392,3038],{"class":372},[362,3394,3041],{"class":384},[362,3396,1911],{"class":739},[362,3398,882],{"class":384},[362,3400,3401,3403],{"class":364,"line":437},[362,3402,2996],{"class":368},[362,3404,1874],{"class":384},[362,3406,3407],{"class":364,"line":444},[362,3408,2816],{"class":384},[10,3410,3411],{},"我们在这里声明一个MF缓冲区的接口指针，这个是media foundation对原始内存的封装，它不止是一个void*，还带有管理内存长度、最大程度和提供lock机制的功能。",[10,3413,3414,3415,3418],{},"然后在系统内存中分配一块大小为packedSize的连续空间。之所以使用它而不是new BYTE()是因为mf的后续组件比如编码器只认识IMFMediaBuffer，",[14,3416,3417],{},"它是COM对象","，当你把它塞入IMFSample之后，就算函数结束了，只有编码器还没处理完就不会销毁内存。这个函数通常会返回一块经过SSE\u002FAVX指令集优化对齐的内存，对后续的memcpy性能有好处。",[353,3420,3422],{"className":355,"code":3421,"language":357,"meta":358,"style":358},"   BYTE* dst = nullptr;\n   hr = buf->Lock(&dst, nullptr, nullptr);\n   if (FAILED(hr)) {\n       m_pImmediateContext->Unmap(m_stagingReadback.Get(), 0);\n       return hr;\n   }\n",[21,3423,3424,3440,3468,3479,3496,3503],{"__ignoreMap":358},[362,3425,3426,3429,3431,3434,3436,3438],{"class":364,"line":365},[362,3427,3428],{"class":384},"   BYTE",[362,3430,1497],{"class":368},[362,3432,3433],{"class":384}," dst ",[362,3435,736],{"class":368},[362,3437,1448],{"class":739},[362,3439,743],{"class":384},[362,3441,3442,3445,3447,3450,3453,3455,3457,3460,3462,3464,3466],{"class":364,"line":376},[362,3443,3444],{"class":384},"   hr ",[362,3446,736],{"class":368},[362,3448,3449],{"class":384}," buf->",[362,3451,3452],{"class":372},"Lock",[362,3454,385],{"class":384},[362,3456,876],{"class":368},[362,3458,3459],{"class":384},"dst, ",[362,3461,1832],{"class":739},[362,3463,391],{"class":384},[362,3465,1832],{"class":739},[362,3467,882],{"class":384},[362,3469,3470,3473,3475,3477],{"class":364,"line":401},[362,3471,3472],{"class":368},"   if",[362,3474,1863],{"class":384},[362,3476,1866],{"class":372},[362,3478,3062],{"class":384},[362,3480,3481,3484,3486,3488,3490,3492,3494],{"class":364,"line":419},[362,3482,3483],{"class":384},"       m_pImmediateContext->",[362,3485,3388],{"class":372},[362,3487,3240],{"class":384},[362,3489,3038],{"class":372},[362,3491,3041],{"class":384},[362,3493,1911],{"class":739},[362,3495,882],{"class":384},[362,3497,3498,3501],{"class":364,"line":437},[362,3499,3500],{"class":368},"       return",[362,3502,1874],{"class":384},[362,3504,3505],{"class":364,"line":444},[362,3506,3507],{"class":384},"   }\n",[10,3509,3510,3511,3513,3514],{},"dst是一个BYTE* 指针，调用Lock之后，系统会在该缓冲区区在进程地址空间中的真实起始地址赋值给dst，",[21,3512,3452],{}," 函数的标准签名是： ",[21,3515,3516],{},"HRESULT Lock(BYTE ppbBuffer, DWORD *pcbMaxLength, DWORD *pcbCurrentLength);",[10,3518,3519],{},"第一个nullptr用于查询缓冲区的最大容量，第二个是目前里面装了的量。",[10,3521,3522],{},"接下来我们会进行搬运：",[353,3524,3526],{"className":355,"code":3525,"language":357,"meta":358,"style":358},"    const auto* src = static_cast\u003Cconst BYTE*>(map.pData);\n    for (UINT y = 0; y \u003C m_config.height; ++y)\n        std::memcpy(dst + static_cast\u003Csize_t>(y) * packedPitch, src + static_cast\u003Csize_t>(y) * map.RowPitch, packedPitch);\n    buf->Unlock();\n    buf->SetCurrentLength(packedSize);\n    m_pImmediateContext->Unmap(m_stagingReadback.Get(), 0);\n",[21,3527,3528,3552,3578,3615,3625,3635],{"__ignoreMap":358},[362,3529,3530,3532,3535,3538,3540,3543,3546,3549],{"class":364,"line":365},[362,3531,2853],{"class":368},[362,3533,3534],{"class":368}," auto*",[362,3536,3537],{"class":384}," src ",[362,3539,736],{"class":368},[362,3541,3542],{"class":368}," static_cast\u003Cconst",[362,3544,3545],{"class":384}," BYTE",[362,3547,3548],{"class":368},"*>",[362,3550,3551],{"class":384},"(map.pData);\n",[362,3553,3554,3557,3560,3562,3564,3567,3569,3572,3575],{"class":364,"line":376},[362,3555,3556],{"class":368},"    for",[362,3558,3559],{"class":384}," (UINT y ",[362,3561,736],{"class":368},[362,3563,1075],{"class":739},[362,3565,3566],{"class":384},"; y ",[362,3568,659],{"class":368},[362,3570,3571],{"class":384}," m_config.height; ",[362,3573,3574],{"class":368},"++",[362,3576,3577],{"class":384},"y)\n",[362,3579,3580,3583,3585,3587,3590,3593,3596,3599,3601,3604,3606,3608,3610,3612],{"class":364,"line":401},[362,3581,3582],{"class":372},"        std",[362,3584,631],{"class":384},[362,3586,2675],{"class":372},[362,3588,3589],{"class":384},"(dst ",[362,3591,3592],{"class":368},"+",[362,3594,3595],{"class":368}," static_cast\u003Csize_t>",[362,3597,3598],{"class":384},"(y) ",[362,3600,1497],{"class":368},[362,3602,3603],{"class":384}," packedPitch, src ",[362,3605,3592],{"class":368},[362,3607,3595],{"class":368},[362,3609,3598],{"class":384},[362,3611,1497],{"class":368},[362,3613,3614],{"class":384}," map.RowPitch, packedPitch);\n",[362,3616,3617,3620,3623],{"class":364,"line":419},[362,3618,3619],{"class":384},"    buf->",[362,3621,3622],{"class":372},"Unlock",[362,3624,891],{"class":384},[362,3626,3627,3629,3632],{"class":364,"line":437},[362,3628,3619],{"class":384},[362,3630,3631],{"class":372},"SetCurrentLength",[362,3633,3634],{"class":384},"(packedSize);\n",[362,3636,3637,3640,3642,3644,3646,3648,3650],{"class":364,"line":444},[362,3638,3639],{"class":384},"    m_pImmediateContext->",[362,3641,3388],{"class":372},[362,3643,3240],{"class":384},[362,3645,3038],{"class":372},[362,3647,3041],{"class":384},[362,3649,1911],{"class":739},[362,3651,882],{"class":384},[10,3653,3654],{},"我们会从src的y行开始，切下packedPitch宽度的有效像素，跳过map.RowPitch中多余的填充字节，然后紧密贴到dst的下一行。Unlock操作提交更改，使用SetCurrentLenghth更新元数据，因为MFCreateMemoryBuffer只是分了控件，而不知道有多少东西，这行代码会告诉后续的Sample这个Buffer有packedSize字节的有效视频数据。",[10,3656,3657],{},"最后使用Unmap归还GPU资源，这一步很重要，忘了写的话会导致显卡驱动认为还在占用资源导致后续copy无法执行从而导致D3D设备重置。",[99,3659],{},[118,3661,3663,3665],{"id":3662},"pushaudiodata与仓库一致的完整实现",[21,3664,173],{},"：与仓库一致的完整实现",[353,3667,3669],{"className":355,"code":3668,"language":357,"meta":358,"style":358},"HRESULT MediaRecorder::PushAudioData(const uint8_t* data, size_t size, uint64_t qpcTime) {\n    if (!m_hasAudioStream)\n        return S_OK;\n    if (!m_isRecording || !m_sinkWriter) return E_FAIL;\n    if (m_startTimeQPC == 0) {\n        m_startTimeQPC = qpcTime;\n    }\n    LONGLONG pts = (qpcTime - m_startTimeQPC) * 10000000 \u002F m_qpcFrequency;\n    ComPtr\u003CIMFMediaBuffer> pBuffer;\n    HRESULT hr = MFCreateMemoryBuffer(static_cast\u003CDWORD>(size), &pBuffer);\n    if (FAILED(hr)) return hr;\n    BYTE* pDest = nullptr;\n    hr = pBuffer->Lock(&pDest, nullptr, nullptr);\n    if (SUCCEEDED(hr)) {\n        memcpy(pDest, data, size);\n        pBuffer->Unlock();\n        pBuffer->SetCurrentLength(static_cast\u003CDWORD>(size));\n    } else {\n        return hr;\n    }\n    ComPtr\u003CIMFSample> pSample;\n    hr = MFCreateSample(&pSample);\n    if (FAILED(hr)) return hr;\n    hr = pSample->AddBuffer(pBuffer.Get());\n    if (FAILED(hr)) return hr;\n    hr = pSample->SetSampleTime(pts);\n    hr = m_sinkWriter->WriteSample(m_audioStreamIndex, pSample.Get());\n    return hr;\n}\n",[21,3670,3671,3703,3714,3720,3740,3752,3760,3764,3788,3798,3825,3839,3855,3881,3892,3900,3909,3926,3936,3942,3946,3957,3973,3987,4006,4020,4034,4051,4057],{"__ignoreMap":358},[362,3672,3673,3675,3677,3679,3681,3683,3685,3687,3689,3691,3693,3695,3697,3699,3701],{"class":364,"line":365},[362,3674,1700],{"class":372},[362,3676,836],{"class":372},[362,3678,631],{"class":384},[362,3680,173],{"class":372},[362,3682,385],{"class":384},[362,3684,667],{"class":368},[362,3686,670],{"class":368},[362,3688,1006],{"class":866},[362,3690,391],{"class":384},[362,3692,1011],{"class":368},[362,3694,1014],{"class":866},[362,3696,391],{"class":384},[362,3698,984],{"class":368},[362,3700,987],{"class":866},[362,3702,2731],{"class":384},[362,3704,3705,3707,3709,3711],{"class":364,"line":376},[362,3706,1860],{"class":368},[362,3708,1863],{"class":384},[362,3710,2740],{"class":368},[362,3712,3713],{"class":384},"m_hasAudioStream)\n",[362,3715,3716,3718],{"class":364,"line":401},[362,3717,2996],{"class":368},[362,3719,2237],{"class":384},[362,3721,3722,3724,3726,3728,3730,3732,3734,3736,3738],{"class":364,"line":419},[362,3723,1860],{"class":368},[362,3725,1863],{"class":384},[362,3727,2740],{"class":368},[362,3729,2743],{"class":384},[362,3731,2746],{"class":368},[362,3733,2749],{"class":368},[362,3735,2752],{"class":384},[362,3737,1511],{"class":368},[362,3739,2757],{"class":384},[362,3741,3742,3744,3746,3748,3750],{"class":364,"line":437},[362,3743,1860],{"class":368},[362,3745,2827],{"class":384},[362,3747,2830],{"class":368},[362,3749,1075],{"class":739},[362,3751,2731],{"class":384},[362,3753,3754,3756,3758],{"class":364,"line":444},[362,3755,2839],{"class":384},[362,3757,736],{"class":368},[362,3759,2844],{"class":384},[362,3761,3762],{"class":364,"line":453},[362,3763,2816],{"class":384},[362,3765,3766,3769,3771,3773,3775,3777,3779,3781,3783,3785],{"class":364,"line":461},[362,3767,3768],{"class":372},"    LONGLONG",[362,3770,2859],{"class":384},[362,3772,736],{"class":368},[362,3774,2864],{"class":384},[362,3776,2867],{"class":368},[362,3778,2870],{"class":384},[362,3780,1497],{"class":368},[362,3782,740],{"class":739},[362,3784,2877],{"class":368},[362,3786,3787],{"class":384}," m_qpcFrequency;\n",[362,3789,3790,3792,3794,3796],{"class":364,"line":469},[362,3791,1190],{"class":372},[362,3793,659],{"class":384},[362,3795,3014],{"class":372},[362,3797,3017],{"class":384},[362,3799,3800,3802,3804,3806,3808,3810,3813,3816,3818,3821,3823],{"class":364,"line":477},[362,3801,928],{"class":372},[362,3803,1816],{"class":384},[362,3805,736],{"class":368},[362,3807,3362],{"class":372},[362,3809,385],{"class":384},[362,3811,3812],{"class":368},"static_cast\u003C",[362,3814,3815],{"class":384},"DWORD",[362,3817,2362],{"class":368},[362,3819,3820],{"class":384},"(size), ",[362,3822,876],{"class":368},[362,3824,3051],{"class":384},[362,3826,3827,3829,3831,3833,3835,3837],{"class":364,"line":482},[362,3828,1860],{"class":368},[362,3830,1863],{"class":384},[362,3832,1866],{"class":372},[362,3834,1869],{"class":384},[362,3836,1511],{"class":368},[362,3838,1874],{"class":384},[362,3840,3841,3844,3846,3849,3851,3853],{"class":364,"line":490},[362,3842,3843],{"class":372},"    BYTE",[362,3845,1497],{"class":368},[362,3847,3848],{"class":384}," pDest ",[362,3850,736],{"class":368},[362,3852,1448],{"class":739},[362,3854,743],{"class":384},[362,3856,3857,3859,3861,3864,3866,3868,3870,3873,3875,3877,3879],{"class":364,"line":498},[362,3858,1992],{"class":384},[362,3860,736],{"class":368},[362,3862,3863],{"class":384}," pBuffer->",[362,3865,3452],{"class":372},[362,3867,385],{"class":384},[362,3869,876],{"class":368},[362,3871,3872],{"class":384},"pDest, ",[362,3874,1832],{"class":739},[362,3876,391],{"class":384},[362,3878,1832],{"class":739},[362,3880,882],{"class":384},[362,3882,3883,3885,3887,3890],{"class":364,"line":506},[362,3884,1860],{"class":368},[362,3886,1863],{"class":384},[362,3888,3889],{"class":372},"SUCCEEDED",[362,3891,3062],{"class":384},[362,3893,3894,3897],{"class":364,"line":514},[362,3895,3896],{"class":372},"        memcpy",[362,3898,3899],{"class":384},"(pDest, data, size);\n",[362,3901,3902,3905,3907],{"class":364,"line":522},[362,3903,3904],{"class":384},"        pBuffer->",[362,3906,3622],{"class":372},[362,3908,891],{"class":384},[362,3910,3911,3913,3915,3917,3919,3921,3923],{"class":364,"line":527},[362,3912,3904],{"class":384},[362,3914,3631],{"class":372},[362,3916,385],{"class":384},[362,3918,3812],{"class":368},[362,3920,3815],{"class":384},[362,3922,2362],{"class":368},[362,3924,3925],{"class":384},"(size));\n",[362,3927,3928,3931,3934],{"class":364,"line":535},[362,3929,3930],{"class":384},"    } ",[362,3932,3933],{"class":368},"else",[362,3935,700],{"class":384},[362,3937,3938,3940],{"class":364,"line":543},[362,3939,2996],{"class":368},[362,3941,1874],{"class":384},[362,3943,3944],{"class":364,"line":551},[362,3945,2816],{"class":384},[362,3947,3948,3950,3952,3954],{"class":364,"line":556},[362,3949,1190],{"class":372},[362,3951,659],{"class":384},[362,3953,1692],{"class":372},[362,3955,3956],{"class":384},"> pSample;\n",[362,3958,3959,3961,3963,3966,3968,3970],{"class":364,"line":564},[362,3960,1992],{"class":384},[362,3962,736],{"class":368},[362,3964,3965],{"class":372}," MFCreateSample",[362,3967,385],{"class":384},[362,3969,876],{"class":368},[362,3971,3972],{"class":384},"pSample);\n",[362,3974,3975,3977,3979,3981,3983,3985],{"class":364,"line":569},[362,3976,1860],{"class":368},[362,3978,1863],{"class":384},[362,3980,1866],{"class":372},[362,3982,1869],{"class":384},[362,3984,1511],{"class":368},[362,3986,1874],{"class":384},[362,3988,3989,3991,3993,3996,3999,4002,4004],{"class":364,"line":577},[362,3990,1992],{"class":384},[362,3992,736],{"class":368},[362,3994,3995],{"class":384}," pSample->",[362,3997,3998],{"class":372},"AddBuffer",[362,4000,4001],{"class":384},"(pBuffer.",[362,4003,3038],{"class":372},[362,4005,1855],{"class":384},[362,4007,4008,4010,4012,4014,4016,4018],{"class":364,"line":585},[362,4009,1860],{"class":368},[362,4011,1863],{"class":384},[362,4013,1866],{"class":372},[362,4015,1869],{"class":384},[362,4017,1511],{"class":368},[362,4019,1874],{"class":384},[362,4021,4022,4024,4026,4028,4031],{"class":364,"line":593},[362,4023,1992],{"class":384},[362,4025,736],{"class":368},[362,4027,3995],{"class":384},[362,4029,4030],{"class":372},"SetSampleTime",[362,4032,4033],{"class":384},"(pts);\n",[362,4035,4036,4038,4040,4042,4044,4047,4049],{"class":364,"line":601},[362,4037,1992],{"class":384},[362,4039,736],{"class":368},[362,4041,3119],{"class":384},[362,4043,2470],{"class":372},[362,4045,4046],{"class":384},"(m_audioStreamIndex, pSample.",[362,4048,3038],{"class":372},[362,4050,1855],{"class":384},[362,4052,4053,4055],{"class":364,"line":609},[362,4054,2234],{"class":368},[362,4056,1874],{"class":384},[362,4058,4059],{"class":364,"line":617},[362,4060,2242],{"class":384},[10,4062,4063,4064,4069],{},"音频轨 ",[14,4065,4066],{},[21,4067,4068],{},"SetSampleDuration"," 未设：当前 AAC MFT 仍可从 buffer 长度与 PCM 格式推断；若你以后遇到音频时钟漂移，再考虑补 duration 或按采样点数累计 PTS。",[99,4071],{},[102,4073,4075,4076,4079],{"id":4074},"播放设备友好名称与稳定-idaudio_output_devicescpp","播放设备「友好名称」与稳定 ID（",[21,4077,4078],{},"audio_output_devices.cpp","）",[10,4081,4082,4083,4088,4089,4092,4093,4098,4099,4102,4103,252],{},"loopback 要选「哪一块声卡\u002F扬声器」，不能只显示 ",[14,4084,4085],{},[21,4086,4087],{},"GetId"," 返回的长字符串——用户需要的是 ",[14,4090,4091],{},"友好名称","（控制面板里那一串），程序需要的是 ",[14,4094,4095],{},[21,4096,4097],{},"IMMDevice::GetId"," 同款 ",[14,4100,4101],{},"实例路径","，传给 ",[14,4104,4105],{},[21,4106,4107],{},"WasapiLoopback::Initialize(deviceId)",[118,4109,4111,4112],{"id":4110},"头文件audiooutputdeviceinfo","头文件：",[21,4113,4114],{},"AudioOutputDeviceInfo",[353,4116,4118],{"className":355,"code":4117,"language":357,"meta":358,"style":358},"#pragma once\n\n#include \u003Cstring>\n#include \u003Cvector>\n\nstruct AudioOutputDeviceInfo {\n    std::wstring id;\n    std::wstring friendlyName;\n};\n\n\u002F** 枚举当前可用的音频播放（渲染）端点，用于 loopback 录制选择设备。须在 COM 已初始化的线程调用。 *\u002F\nstd::vector\u003CAudioOutputDeviceInfo> EnumerateAudioRenderDevices();\n",[21,4119,4120,4126,4130,4136,4143,4147,4156,4167,4178,4182,4186,4191],{"__ignoreMap":358},[362,4121,4122,4124],{"class":364,"line":365},[362,4123,369],{"class":368},[362,4125,373],{"class":372},[362,4127,4128],{"class":364,"line":376},[362,4129,441],{"emptyLinePlaceholder":440},[362,4131,4132,4134],{"class":364,"line":401},[362,4133,447],{"class":368},[362,4135,590],{"class":394},[362,4137,4138,4140],{"class":364,"line":419},[362,4139,447],{"class":368},[362,4141,4142],{"class":394}," \u003Cvector>\n",[362,4144,4145],{"class":364,"line":437},[362,4146,441],{"emptyLinePlaceholder":440},[362,4148,4149,4151,4154],{"class":364,"line":444},[362,4150,694],{"class":368},[362,4152,4153],{"class":372}," AudioOutputDeviceInfo",[362,4155,700],{"class":384},[362,4157,4158,4160,4162,4164],{"class":364,"line":453},[362,4159,808],{"class":372},[362,4161,631],{"class":384},[362,4163,813],{"class":372},[362,4165,4166],{"class":384}," id;\n",[362,4168,4169,4171,4173,4175],{"class":364,"line":461},[362,4170,808],{"class":372},[362,4172,631],{"class":384},[362,4174,813],{"class":372},[362,4176,4177],{"class":384}," friendlyName;\n",[362,4179,4180],{"class":364,"line":469},[362,4181,822],{"class":384},[362,4183,4184],{"class":364,"line":477},[362,4185,441],{"emptyLinePlaceholder":440},[362,4187,4188],{"class":364,"line":482},[362,4189,4190],{"class":779},"\u002F** 枚举当前可用的音频播放（渲染）端点，用于 loopback 录制选择设备。须在 COM 已初始化的线程调用。 *\u002F\n",[362,4192,4193,4196,4198,4201,4203,4205,4207,4210],{"class":364,"line":490},[362,4194,4195],{"class":372},"std",[362,4197,631],{"class":384},[362,4199,4200],{"class":372},"vector",[362,4202,659],{"class":384},[362,4204,4114],{"class":372},[362,4206,863],{"class":384},[362,4208,4209],{"class":372},"EnumerateAudioRenderDevices",[362,4211,891],{"class":384},[223,4213,4214,4235],{},[226,4215,4216,2662,4221,4225,4226,151,4230,4234],{},[14,4217,4218],{},[21,4219,4220],{},"id",[14,4222,4223],{},[21,4224,4097],{},"，UTF-16；与 WASAPI ",[14,4227,4228],{},[21,4229,1940],{},[14,4231,4232],{},[21,4233,1756],{}," 用的是同一个 token。",[226,4236,4237,4242,4243,4248,4249,4254],{},[14,4238,4239],{},[21,4240,4241],{},"friendlyName","：属性 ",[14,4244,4245],{},[21,4246,4247],{},"PKEY_Device_FriendlyName","，一般是人类可读名称；若属性读取失败，仓库里 ",[14,4250,4251,4252],{},"退回用 ",[21,4253,4220],{}," 占位，避免空白项。",[118,4256,4258],{"id":4257},"枚举实现与仓库一致","枚举实现（与仓库一致）",[353,4260,4262],{"className":355,"code":4261,"language":357,"meta":358,"style":358},"#include \"audio_output_devices.h\"\n\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif\n\n#include \u003CWindows.h>\n#include \u003Cmmdeviceapi.h>\n#include \u003Cpropsys.h>\n#include \u003CFunctiondiscoverykeys_devpkey.h>\n\n#include \u003Cwrl\u002Fclient.h>\n\n#pragma comment(lib, \"propsys.lib\")\n\nusing Microsoft::WRL::ComPtr;\n\nstd::vector\u003CAudioOutputDeviceInfo> EnumerateAudioRenderDevices()\n{\n    std::vector\u003CAudioOutputDeviceInfo> list;\n\n    const HRESULT coin = CoInitializeEx(nullptr, COINIT_MULTITHREADED);\n    if (FAILED(coin) && coin != RPC_E_CHANGED_MODE)\n        return list;\n    const bool coinit_ok = SUCCEEDED(coin);\n\n    ComPtr\u003CIMMDeviceEnumerator> enumerator;\n    HRESULT hr = CoCreateInstance(\n        __uuidof(MMDeviceEnumerator),\n        nullptr,\n        CLSCTX_ALL,\n        __uuidof(IMMDeviceEnumerator),\n        enumerator.ReleaseAndGetAddressOf());\n    if (FAILED(hr)) {\n        if (coinit_ok)\n            CoUninitialize();\n        return list;\n    }\n\n    ComPtr\u003CIMMDeviceCollection> collection;\n    hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, collection.GetAddressOf());\n    if (FAILED(hr)) {\n        if (coinit_ok)\n            CoUninitialize();\n        return list;\n    }\n\n    UINT count = 0;\n    collection->GetCount(&count);\n    for (UINT i = 0; i \u003C count; ++i) {\n        ComPtr\u003CIMMDevice> device;\n        hr = collection->Item(i, device.GetAddressOf());\n        if (FAILED(hr))\n            continue;\n\n        LPWSTR idRaw = nullptr;\n        if (FAILED(device->GetId(&idRaw)) || !idRaw)\n            continue;\n        std::wstring id(idRaw);\n        CoTaskMemFree(idRaw);\n\n        ComPtr\u003CIPropertyStore> props;\n        std::wstring friendly = id;\n        if (SUCCEEDED(device->OpenPropertyStore(STGM_READ, props.GetAddressOf()))) {\n            PROPVARIANT pv{};\n            PropVariantInit(&pv);\n            if (SUCCEEDED(props->GetValue(PKEY_Device_FriendlyName, &pv)) && pv.vt == VT_LPWSTR && pv.pwszVal)\n                friendly = pv.pwszVal;\n            PropVariantClear(&pv);\n        }\n\n        list.push_back(AudioOutputDeviceInfo{ std::move(id), std::move(friendly) });\n    }\n\n    if (coinit_ok)\n        CoUninitialize();\n    return list;\n}\n",[21,4263,4264,4271,4275,4283,4290,4295,4302,4308,4312,4316,4323,4329,4336,4342,4346,4352,4356,4373,4377,4389,4393,4412,4416,4431,4435,4457,4477,4484,4502,4506,4517,4529,4536,4542,4547,4554,4564,4574,4581,4588,4594,4598,4602,4614,4633,4643,4649,4655,4661,4665,4669,4683,4698,4722,4734,4753,4764,4771,4775,4789,4816,4822,4836,4843,4847,4859,4874,4895,4903,4915,4953,4963,4974,4978,4982,5012,5016,5020,5026,5033,5039],{"__ignoreMap":358},[362,4265,4266,4268],{"class":364,"line":365},[362,4267,447],{"class":368},[362,4269,4270],{"class":394}," \"audio_output_devices.h\"\n",[362,4272,4273],{"class":364,"line":376},[362,4274,441],{"emptyLinePlaceholder":440},[362,4276,4277,4280],{"class":364,"line":401},[362,4278,4279],{"class":368},"#ifndef",[362,4281,4282],{"class":372}," WIN32_LEAN_AND_MEAN\n",[362,4284,4285,4288],{"class":364,"line":419},[362,4286,4287],{"class":368},"#define",[362,4289,4282],{"class":372},[362,4291,4292],{"class":364,"line":437},[362,4293,4294],{"class":368},"#endif\n",[362,4296,4297,4299],{"class":364,"line":444},[362,4298,4279],{"class":368},[362,4300,4301],{"class":372}," NOMINMAX\n",[362,4303,4304,4306],{"class":364,"line":453},[362,4305,4287],{"class":368},[362,4307,4301],{"class":372},[362,4309,4310],{"class":364,"line":461},[362,4311,4294],{"class":368},[362,4313,4314],{"class":364,"line":469},[362,4315,441],{"emptyLinePlaceholder":440},[362,4317,4318,4320],{"class":364,"line":477},[362,4319,447],{"class":368},[362,4321,4322],{"class":394}," \u003CWindows.h>\n",[362,4324,4325,4327],{"class":364,"line":482},[362,4326,447],{"class":368},[362,4328,503],{"class":394},[362,4330,4331,4333],{"class":364,"line":490},[362,4332,447],{"class":368},[362,4334,4335],{"class":394}," \u003Cpropsys.h>\n",[362,4337,4338,4340],{"class":364,"line":498},[362,4339,447],{"class":368},[362,4341,519],{"class":394},[362,4343,4344],{"class":364,"line":506},[362,4345,441],{"emptyLinePlaceholder":440},[362,4347,4348,4350],{"class":364,"line":514},[362,4349,447],{"class":368},[362,4351,466],{"class":394},[362,4353,4354],{"class":364,"line":522},[362,4355,441],{"emptyLinePlaceholder":440},[362,4357,4358,4360,4362,4364,4366,4368,4371],{"class":364,"line":527},[362,4359,369],{"class":368},[362,4361,381],{"class":372},[362,4363,385],{"class":384},[362,4365,388],{"class":372},[362,4367,391],{"class":384},[362,4369,4370],{"class":394},"\"propsys.lib\"",[362,4372,398],{"class":384},[362,4374,4375],{"class":364,"line":535},[362,4376,441],{"emptyLinePlaceholder":440},[362,4378,4379,4381,4383,4385,4387],{"class":364,"line":543},[362,4380,625],{"class":368},[362,4382,628],{"class":372},[362,4384,631],{"class":384},[362,4386,634],{"class":372},[362,4388,637],{"class":384},[362,4390,4391],{"class":364,"line":551},[362,4392,441],{"emptyLinePlaceholder":440},[362,4394,4395,4397,4399,4401,4403,4405,4407,4409],{"class":364,"line":556},[362,4396,4195],{"class":372},[362,4398,631],{"class":384},[362,4400,4200],{"class":372},[362,4402,659],{"class":384},[362,4404,4114],{"class":372},[362,4406,863],{"class":384},[362,4408,4209],{"class":372},[362,4410,4411],{"class":384},"()\n",[362,4413,4414],{"class":364,"line":564},[362,4415,1788],{"class":384},[362,4417,4418,4420,4422,4424,4426,4428],{"class":364,"line":569},[362,4419,808],{"class":372},[362,4421,631],{"class":384},[362,4423,4200],{"class":372},[362,4425,659],{"class":384},[362,4427,4114],{"class":372},[362,4429,4430],{"class":384},"> list;\n",[362,4432,4433],{"class":364,"line":577},[362,4434,441],{"emptyLinePlaceholder":440},[362,4436,4437,4439,4442,4445,4447,4450,4452,4454],{"class":364,"line":585},[362,4438,2853],{"class":368},[362,4440,4441],{"class":372}," HRESULT",[362,4443,4444],{"class":384}," coin ",[362,4446,736],{"class":368},[362,4448,4449],{"class":372}," CoInitializeEx",[362,4451,385],{"class":384},[362,4453,1832],{"class":739},[362,4455,4456],{"class":384},", COINIT_MULTITHREADED);\n",[362,4458,4459,4461,4463,4465,4468,4470,4472,4474],{"class":364,"line":593},[362,4460,1860],{"class":368},[362,4462,1863],{"class":384},[362,4464,1866],{"class":372},[362,4466,4467],{"class":384},"(coin) ",[362,4469,2351],{"class":368},[362,4471,4444],{"class":384},[362,4473,1900],{"class":368},[362,4475,4476],{"class":384}," RPC_E_CHANGED_MODE)\n",[362,4478,4479,4481],{"class":364,"line":601},[362,4480,2996],{"class":368},[362,4482,4483],{"class":384}," list;\n",[362,4485,4486,4488,4491,4494,4496,4499],{"class":364,"line":609},[362,4487,2853],{"class":368},[362,4489,4490],{"class":368}," bool",[362,4492,4493],{"class":384}," coinit_ok ",[362,4495,736],{"class":368},[362,4497,4498],{"class":372}," SUCCEEDED",[362,4500,4501],{"class":384},"(coin);\n",[362,4503,4504],{"class":364,"line":617},[362,4505,441],{"emptyLinePlaceholder":440},[362,4507,4508,4510,4512,4514],{"class":364,"line":622},[362,4509,1190],{"class":372},[362,4511,659],{"class":384},[362,4513,1806],{"class":372},[362,4515,4516],{"class":384},"> enumerator;\n",[362,4518,4519,4521,4523,4525,4527],{"class":364,"line":640},[362,4520,928],{"class":372},[362,4522,1816],{"class":384},[362,4524,736],{"class":368},[362,4526,1821],{"class":372},[362,4528,2118],{"class":384},[362,4530,4531,4533],{"class":364,"line":686},[362,4532,1840],{"class":372},[362,4534,4535],{"class":384},"(MMDeviceEnumerator),\n",[362,4537,4538,4540],{"class":364,"line":691},[362,4539,2150],{"class":739},[362,4541,2012],{"class":384},[362,4543,4544],{"class":364,"line":703},[362,4545,4546],{"class":384},"        CLSCTX_ALL,\n",[362,4548,4549,4551],{"class":364,"line":712},[362,4550,1840],{"class":372},[362,4552,4553],{"class":384},"(IMMDeviceEnumerator),\n",[362,4555,4556,4559,4562],{"class":364,"line":720},[362,4557,4558],{"class":384},"        enumerator.",[362,4560,4561],{"class":372},"ReleaseAndGetAddressOf",[362,4563,1855],{"class":384},[362,4565,4566,4568,4570,4572],{"class":364,"line":728},[362,4567,1860],{"class":368},[362,4569,1863],{"class":384},[362,4571,1866],{"class":372},[362,4573,3062],{"class":384},[362,4575,4576,4578],{"class":364,"line":746},[362,4577,3067],{"class":368},[362,4579,4580],{"class":384}," (coinit_ok)\n",[362,4582,4583,4586],{"class":364,"line":761},[362,4584,4585],{"class":372},"            CoUninitialize",[362,4587,891],{"class":384},[362,4589,4590,4592],{"class":364,"line":776},[362,4591,2996],{"class":368},[362,4593,4483],{"class":384},[362,4595,4596],{"class":364,"line":783},[362,4597,2816],{"class":384},[362,4599,4600],{"class":364,"line":799},[362,4601,441],{"emptyLinePlaceholder":440},[362,4603,4604,4606,4608,4611],{"class":364,"line":805},[362,4605,1190],{"class":372},[362,4607,659],{"class":384},[362,4609,4610],{"class":372},"IMMDeviceCollection",[362,4612,4613],{"class":384},"> collection;\n",[362,4615,4616,4618,4620,4623,4626,4629,4631],{"class":364,"line":819},[362,4617,1992],{"class":384},[362,4619,736],{"class":368},[362,4621,4622],{"class":384}," enumerator->",[362,4624,4625],{"class":372},"EnumAudioEndpoints",[362,4627,4628],{"class":384},"(eRender, DEVICE_STATE_ACTIVE, collection.",[362,4630,1852],{"class":372},[362,4632,1855],{"class":384},[362,4634,4635,4637,4639,4641],{"class":364,"line":825},[362,4636,1860],{"class":368},[362,4638,1863],{"class":384},[362,4640,1866],{"class":372},[362,4642,3062],{"class":384},[362,4644,4645,4647],{"class":364,"line":830},[362,4646,3067],{"class":368},[362,4648,4580],{"class":384},[362,4650,4651,4653],{"class":364,"line":841},[362,4652,4585],{"class":372},[362,4654,891],{"class":384},[362,4656,4657,4659],{"class":364,"line":847},[362,4658,2996],{"class":368},[362,4660,4483],{"class":384},[362,4662,4663],{"class":364,"line":885},[362,4664,2816],{"class":384},[362,4666,4667],{"class":364,"line":894},[362,4668,441],{"emptyLinePlaceholder":440},[362,4670,4671,4674,4677,4679,4681],{"class":364,"line":899},[362,4672,4673],{"class":372},"    UINT",[362,4675,4676],{"class":384}," count ",[362,4678,736],{"class":368},[362,4680,1075],{"class":739},[362,4682,743],{"class":384},[362,4684,4685,4688,4691,4693,4695],{"class":364,"line":910},[362,4686,4687],{"class":384},"    collection->",[362,4689,4690],{"class":372},"GetCount",[362,4692,385],{"class":384},[362,4694,876],{"class":368},[362,4696,4697],{"class":384},"count);\n",[362,4699,4700,4702,4705,4707,4709,4712,4714,4717,4719],{"class":364,"line":920},[362,4701,3556],{"class":368},[362,4703,4704],{"class":384}," (UINT i ",[362,4706,736],{"class":368},[362,4708,1075],{"class":739},[362,4710,4711],{"class":384},"; i ",[362,4713,659],{"class":368},[362,4715,4716],{"class":384}," count; ",[362,4718,3574],{"class":368},[362,4720,4721],{"class":384},"i) {\n",[362,4723,4724,4727,4729,4731],{"class":364,"line":925},[362,4725,4726],{"class":372},"        ComPtr",[362,4728,659],{"class":384},[362,4730,1887],{"class":372},[362,4732,4733],{"class":384},"> device;\n",[362,4735,4736,4738,4740,4743,4746,4749,4751],{"class":364,"line":961},[362,4737,1932],{"class":384},[362,4739,736],{"class":368},[362,4741,4742],{"class":384}," collection->",[362,4744,4745],{"class":372},"Item",[362,4747,4748],{"class":384},"(i, device.",[362,4750,1852],{"class":372},[362,4752,1855],{"class":384},[362,4754,4755,4757,4759,4761],{"class":364,"line":992},[362,4756,3067],{"class":368},[362,4758,1863],{"class":384},[362,4760,1866],{"class":372},[362,4762,4763],{"class":384},"(hr))\n",[362,4765,4766,4769],{"class":364,"line":1025},[362,4767,4768],{"class":368},"            continue",[362,4770,743],{"class":384},[362,4772,4773],{"class":364,"line":1036},[362,4774,441],{"emptyLinePlaceholder":440},[362,4776,4777,4780,4783,4785,4787],{"class":364,"line":1041},[362,4778,4779],{"class":372},"        LPWSTR",[362,4781,4782],{"class":384}," idRaw ",[362,4784,736],{"class":368},[362,4786,1448],{"class":739},[362,4788,743],{"class":384},[362,4790,4791,4793,4795,4797,4800,4802,4804,4806,4809,4811,4813],{"class":364,"line":1065},[362,4792,3067],{"class":368},[362,4794,1863],{"class":384},[362,4796,1866],{"class":372},[362,4798,4799],{"class":384},"(device->",[362,4801,4087],{"class":372},[362,4803,385],{"class":384},[362,4805,876],{"class":368},[362,4807,4808],{"class":384},"idRaw)) ",[362,4810,2746],{"class":368},[362,4812,2749],{"class":368},[362,4814,4815],{"class":384},"idRaw)\n",[362,4817,4818,4820],{"class":364,"line":1080},[362,4819,4768],{"class":368},[362,4821,743],{"class":384},[362,4823,4824,4826,4828,4830,4833],{"class":364,"line":1094},[362,4825,3582],{"class":372},[362,4827,631],{"class":384},[362,4829,813],{"class":372},[362,4831,4832],{"class":372}," id",[362,4834,4835],{"class":384},"(idRaw);\n",[362,4837,4838,4841],{"class":364,"line":1099},[362,4839,4840],{"class":372},"        CoTaskMemFree",[362,4842,4835],{"class":384},[362,4844,4845],{"class":364,"line":1105},[362,4846,441],{"emptyLinePlaceholder":440},[362,4848,4849,4851,4853,4856],{"class":364,"line":1125},[362,4850,4726],{"class":372},[362,4852,659],{"class":384},[362,4854,4855],{"class":372},"IPropertyStore",[362,4857,4858],{"class":384},"> props;\n",[362,4860,4861,4863,4865,4867,4870,4872],{"class":364,"line":1145},[362,4862,3582],{"class":372},[362,4864,631],{"class":384},[362,4866,813],{"class":372},[362,4868,4869],{"class":384}," friendly ",[362,4871,736],{"class":368},[362,4873,4166],{"class":384},[362,4875,4876,4878,4880,4882,4884,4887,4890,4892],{"class":364,"line":1182},[362,4877,3067],{"class":368},[362,4879,1863],{"class":384},[362,4881,3889],{"class":372},[362,4883,4799],{"class":384},[362,4885,4886],{"class":372},"OpenPropertyStore",[362,4888,4889],{"class":384},"(STGM_READ, props.",[362,4891,1852],{"class":372},[362,4893,4894],{"class":384},"()))) {\n",[362,4896,4897,4900],{"class":364,"line":1187},[362,4898,4899],{"class":372},"            PROPVARIANT",[362,4901,4902],{"class":384}," pv{};\n",[362,4904,4905,4908,4910,4912],{"class":364,"line":1200},[362,4906,4907],{"class":372},"            PropVariantInit",[362,4909,385],{"class":384},[362,4911,876],{"class":368},[362,4913,4914],{"class":384},"pv);\n",[362,4916,4917,4920,4922,4924,4927,4930,4933,4935,4938,4940,4943,4945,4948,4950],{"class":364,"line":1213},[362,4918,4919],{"class":368},"            if",[362,4921,1863],{"class":384},[362,4923,3889],{"class":372},[362,4925,4926],{"class":384},"(props->",[362,4928,4929],{"class":372},"GetValue",[362,4931,4932],{"class":384},"(PKEY_Device_FriendlyName, ",[362,4934,876],{"class":368},[362,4936,4937],{"class":384},"pv)) ",[362,4939,2351],{"class":368},[362,4941,4942],{"class":384}," pv.vt ",[362,4944,2830],{"class":368},[362,4946,4947],{"class":384}," VT_LPWSTR ",[362,4949,2351],{"class":368},[362,4951,4952],{"class":384}," pv.pwszVal)\n",[362,4954,4955,4958,4960],{"class":364,"line":1225},[362,4956,4957],{"class":384},"                friendly ",[362,4959,736],{"class":368},[362,4961,4962],{"class":384}," pv.pwszVal;\n",[362,4964,4965,4968,4970,4972],{"class":364,"line":1240},[362,4966,4967],{"class":372},"            PropVariantClear",[362,4969,385],{"class":384},[362,4971,876],{"class":368},[362,4973,4914],{"class":384},[362,4975,4976],{"class":364,"line":1253},[362,4977,3095],{"class":384},[362,4979,4980],{"class":364,"line":1265},[362,4981,441],{"emptyLinePlaceholder":440},[362,4983,4984,4987,4990,4993,4995,4997,5000,5003,5005,5007,5009],{"class":364,"line":1270},[362,4985,4986],{"class":384},"        list.",[362,4988,4989],{"class":372},"push_back",[362,4991,4992],{"class":384},"(AudioOutputDeviceInfo{ ",[362,4994,4195],{"class":372},[362,4996,631],{"class":384},[362,4998,4999],{"class":372},"move",[362,5001,5002],{"class":384},"(id), ",[362,5004,4195],{"class":372},[362,5006,631],{"class":384},[362,5008,4999],{"class":372},[362,5010,5011],{"class":384},"(friendly) });\n",[362,5013,5014],{"class":364,"line":1279},[362,5015,2816],{"class":384},[362,5017,5018],{"class":364,"line":1294},[362,5019,441],{"emptyLinePlaceholder":440},[362,5021,5022,5024],{"class":364,"line":1308},[362,5023,1860],{"class":368},[362,5025,4580],{"class":384},[362,5027,5028,5031],{"class":364,"line":1322},[362,5029,5030],{"class":372},"        CoUninitialize",[362,5032,891],{"class":384},[362,5034,5035,5037],{"class":364,"line":1327},[362,5036,2234],{"class":368},[362,5038,4483],{"class":384},[362,5040,5041],{"class":364,"line":1332},[362,5042,2242],{"class":384},[10,5044,5045],{},"由于我们必须使用CoInializeEx让当前线程初始化COM库，这里我们设置为了MTA模式，这样这个线程不仅可以调用COM对象，还可以多个线程同时访问COM对象，不需要windows来进行消息循环进行同步。",[10,5047,5048],{},"coin_ok是为了配套之后的反初始化，先不管。我们使用CoCreateInstance创建COM对象工厂，然后告诉系统创建具体的类，",[10,5050,5051],{},"CoCreateInstance成功的前提是windows音频服务必须开启，如果被禁用了那么就会返回REGDB_E_CLASSNOTREG错误。",[353,5053,5055],{"className":355,"code":5054,"language":357,"meta":358,"style":358},"    ComPtr\u003CIMMDeviceCollection> collection;\n    hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, collection.GetAddressOf()); \u002F\u002Fcolletion接收产出的“设备清单”\n    if (FAILED(hr)) {\n        if (coinit_ok)\n            CoUninitialize();\n        return list;\n    }\n",[21,5056,5057,5070,5090,5100,5106,5112,5118],{"__ignoreMap":358},[362,5058,5059,5061,5063,5065,5067],{"class":364,"line":365},[362,5060,1190],{"class":384},[362,5062,659],{"class":368},[362,5064,4610],{"class":384},[362,5066,2362],{"class":368},[362,5068,5069],{"class":384}," collection;\n",[362,5071,5072,5074,5076,5078,5080,5082,5084,5087],{"class":364,"line":376},[362,5073,1992],{"class":384},[362,5075,736],{"class":368},[362,5077,4622],{"class":384},[362,5079,4625],{"class":372},[362,5081,4628],{"class":384},[362,5083,1852],{"class":372},[362,5085,5086],{"class":384},"());",[362,5088,5089],{"class":779}," \u002F\u002Fcolletion接收产出的“设备清单”\n",[362,5091,5092,5094,5096,5098],{"class":364,"line":401},[362,5093,1860],{"class":368},[362,5095,1863],{"class":384},[362,5097,1866],{"class":372},[362,5099,3062],{"class":384},[362,5101,5102,5104],{"class":364,"line":419},[362,5103,3067],{"class":368},[362,5105,4580],{"class":384},[362,5107,5108,5110],{"class":364,"line":437},[362,5109,4585],{"class":372},[362,5111,891],{"class":384},[362,5113,5114,5116],{"class":364,"line":444},[362,5115,2996],{"class":368},[362,5117,4483],{"class":384},[362,5119,5120],{"class":364,"line":453},[362,5121,2816],{"class":384},[10,5123,5124],{},"我们使用EnumAudioEndpoints枚举音频端点，eRender是定义数据流向，由于我们做的是loopback回访采集，录制的是电脑发声，因此声音是流向扬声器的，所以样定位到这些渲染端点。然后开启它们的环回模式，如果我们选的是eCapture，那么就是麦克风了。",[10,5126,5127,5128,5131],{},"DEVICE_STATE_ACTIVE 是过滤设备状态，列出",[14,5129,5130],{},"当前已插入且可用","的设备。",[353,5133,5135],{"className":355,"code":5134,"language":357,"meta":358,"style":358},"    UINT count = 0;\n    collection->GetCount(&count);\n    for (UINT i = 0; i \u003C count; ++i) {\n        ComPtr\u003CIMMDevice> device;\n        hr = collection->Item(i, device.GetAddressOf());\n        if (FAILED(hr))\n            continue;\n  \n        LPWSTR idRaw = nullptr;\n        if (FAILED(device->GetId(&idRaw)) || !idRaw)\n            continue;\n        std::wstring id(idRaw);\n        CoTaskMemFree(idRaw);\n        ComPtr\u003CIPropertyStore> props;\n        std::wstring friendly = id;\n        if (SUCCEEDED(device->OpenPropertyStore(STGM_READ, props.GetAddressOf()))) {\n            PROPVARIANT pv{};\n            PropVariantInit(&pv);\n            if (SUCCEEDED(props->GetValue(PKEY_Device_FriendlyName, &pv)) && pv.vt == VT_LPWSTR && pv.pwszVal)\n                friendly = pv.pwszVal;\n            PropVariantClear(&pv);\n        }  \n        list.push_back(AudioOutputDeviceInfo{ std::move(id), std::move(friendly) });\n    }\n",[21,5136,5137,5148,5161,5182,5193,5210,5221,5228,5233,5246,5270,5276,5289,5296,5306,5320,5338,5345,5356,5387,5396,5407,5412,5437],{"__ignoreMap":358},[362,5138,5139,5142,5144,5146],{"class":364,"line":365},[362,5140,5141],{"class":384},"    UINT count ",[362,5143,736],{"class":368},[362,5145,1075],{"class":739},[362,5147,743],{"class":384},[362,5149,5150,5153,5155,5157,5159],{"class":364,"line":376},[362,5151,5152],{"class":384},"    collection->",[362,5154,4690],{"class":372},[362,5156,385],{"class":384},[362,5158,876],{"class":368},[362,5160,4697],{"class":384},[362,5162,5163,5166,5168,5170,5172,5174,5176,5178,5180],{"class":364,"line":401},[362,5164,5165],{"class":368},"    for",[362,5167,4704],{"class":384},[362,5169,736],{"class":368},[362,5171,1075],{"class":739},[362,5173,4711],{"class":384},[362,5175,659],{"class":368},[362,5177,4716],{"class":384},[362,5179,3574],{"class":368},[362,5181,4721],{"class":384},[362,5183,5184,5187,5189,5191],{"class":364,"line":419},[362,5185,5186],{"class":372},"        ComPtr",[362,5188,659],{"class":384},[362,5190,1887],{"class":372},[362,5192,4733],{"class":384},[362,5194,5195,5198,5200,5202,5204,5206,5208],{"class":364,"line":437},[362,5196,5197],{"class":384},"        hr ",[362,5199,736],{"class":368},[362,5201,4742],{"class":384},[362,5203,4745],{"class":372},[362,5205,4748],{"class":384},[362,5207,1852],{"class":372},[362,5209,1855],{"class":384},[362,5211,5212,5215,5217,5219],{"class":364,"line":444},[362,5213,5214],{"class":368},"        if",[362,5216,1863],{"class":384},[362,5218,1866],{"class":372},[362,5220,4763],{"class":384},[362,5222,5223,5226],{"class":364,"line":453},[362,5224,5225],{"class":368},"            continue",[362,5227,743],{"class":384},[362,5229,5230],{"class":364,"line":461},[362,5231,5232],{"class":384},"  \n",[362,5234,5235,5238,5240,5242,5244],{"class":364,"line":469},[362,5236,5237],{"class":372},"        LPWSTR",[362,5239,4782],{"class":384},[362,5241,736],{"class":368},[362,5243,1448],{"class":739},[362,5245,743],{"class":384},[362,5247,5248,5250,5252,5254,5256,5258,5260,5262,5264,5266,5268],{"class":364,"line":477},[362,5249,5214],{"class":368},[362,5251,1863],{"class":384},[362,5253,1866],{"class":372},[362,5255,4799],{"class":384},[362,5257,4087],{"class":372},[362,5259,385],{"class":384},[362,5261,876],{"class":368},[362,5263,4808],{"class":384},[362,5265,2746],{"class":368},[362,5267,2749],{"class":368},[362,5269,4815],{"class":384},[362,5271,5272,5274],{"class":364,"line":482},[362,5273,5225],{"class":368},[362,5275,743],{"class":384},[362,5277,5278,5281,5283,5285,5287],{"class":364,"line":490},[362,5279,5280],{"class":372},"        std",[362,5282,631],{"class":384},[362,5284,813],{"class":372},[362,5286,4832],{"class":372},[362,5288,4835],{"class":384},[362,5290,5291,5294],{"class":364,"line":498},[362,5292,5293],{"class":372},"        CoTaskMemFree",[362,5295,4835],{"class":384},[362,5297,5298,5300,5302,5304],{"class":364,"line":506},[362,5299,5186],{"class":372},[362,5301,659],{"class":384},[362,5303,4855],{"class":372},[362,5305,4858],{"class":384},[362,5307,5308,5310,5312,5314,5316,5318],{"class":364,"line":514},[362,5309,5280],{"class":372},[362,5311,631],{"class":384},[362,5313,813],{"class":372},[362,5315,4869],{"class":384},[362,5317,736],{"class":368},[362,5319,4166],{"class":384},[362,5321,5322,5324,5326,5328,5330,5332,5334,5336],{"class":364,"line":522},[362,5323,5214],{"class":368},[362,5325,1863],{"class":384},[362,5327,3889],{"class":372},[362,5329,4799],{"class":384},[362,5331,4886],{"class":372},[362,5333,4889],{"class":384},[362,5335,1852],{"class":372},[362,5337,4894],{"class":384},[362,5339,5340,5343],{"class":364,"line":527},[362,5341,5342],{"class":372},"            PROPVARIANT",[362,5344,4902],{"class":384},[362,5346,5347,5350,5352,5354],{"class":364,"line":535},[362,5348,5349],{"class":372},"            PropVariantInit",[362,5351,385],{"class":384},[362,5353,876],{"class":368},[362,5355,4914],{"class":384},[362,5357,5358,5361,5363,5365,5367,5369,5371,5373,5375,5377,5379,5381,5383,5385],{"class":364,"line":543},[362,5359,5360],{"class":368},"            if",[362,5362,1863],{"class":384},[362,5364,3889],{"class":372},[362,5366,4926],{"class":384},[362,5368,4929],{"class":372},[362,5370,4932],{"class":384},[362,5372,876],{"class":368},[362,5374,4937],{"class":384},[362,5376,2351],{"class":368},[362,5378,4942],{"class":384},[362,5380,2830],{"class":368},[362,5382,4947],{"class":384},[362,5384,2351],{"class":368},[362,5386,4952],{"class":384},[362,5388,5389,5392,5394],{"class":364,"line":551},[362,5390,5391],{"class":384},"                friendly ",[362,5393,736],{"class":368},[362,5395,4962],{"class":384},[362,5397,5398,5401,5403,5405],{"class":364,"line":556},[362,5399,5400],{"class":372},"            PropVariantClear",[362,5402,385],{"class":384},[362,5404,876],{"class":368},[362,5406,4914],{"class":384},[362,5408,5409],{"class":364,"line":564},[362,5410,5411],{"class":384},"        }  \n",[362,5413,5414,5417,5419,5421,5423,5425,5427,5429,5431,5433,5435],{"class":364,"line":569},[362,5415,5416],{"class":384},"        list.",[362,5418,4989],{"class":372},[362,5420,4992],{"class":384},[362,5422,4195],{"class":372},[362,5424,631],{"class":384},[362,5426,4999],{"class":372},[362,5428,5002],{"class":384},[362,5430,4195],{"class":372},[362,5432,631],{"class":384},[362,5434,4999],{"class":372},[362,5436,5011],{"class":384},[362,5438,5439],{"class":364,"line":577},[362,5440,5441],{"class":384},"    }\n",[10,5443,5444],{},"在windows的驱动模型中，设备会被抽象为一组键值对，IPropertyStore存储的是设备信息。PROPVARIANT是COM的容器，由于属性可能是字符串、整数甚至是日期，那么这个容器就必须容纳这些不同类型的数据，我们在这里通过pv.vt == VT_LPWSTR来确认他是一个宽字符字符串。PKEY_Device_FriendlyName 是索引，存放设备在控制面板里显示的名字。",[10,5446,5447,5448,5451],{},"使用",[21,5449,5450],{},"CoTaskMemFree(idRaw)"," 去COM堆上分配一块内存并返回地址作为调用者，我们需要手动调用CoTaskMemFree来归还内存，如果不写的话就会导致内存泄漏。",[10,5453,5454],{},"最核心的就是这段代码：",[353,5456,5458],{"className":355,"code":5457,"language":357,"meta":358,"style":358},"        if (SUCCEEDED(device->OpenPropertyStore(STGM_READ, props.GetAddressOf()))) {\n            PROPVARIANT pv{};\n            PropVariantInit(&pv);\n            if (SUCCEEDED(props->GetValue(PKEY_Device_FriendlyName, &pv)) && pv.vt == VT_LPWSTR && pv.pwszVal)\n                friendly = pv.pwszVal;\n            PropVariantClear(&pv);\n        }\n",[21,5459,5460,5478,5484,5494,5524,5532,5542],{"__ignoreMap":358},[362,5461,5462,5464,5466,5468,5470,5472,5474,5476],{"class":364,"line":365},[362,5463,3067],{"class":368},[362,5465,1863],{"class":384},[362,5467,3889],{"class":372},[362,5469,4799],{"class":384},[362,5471,4886],{"class":372},[362,5473,4889],{"class":384},[362,5475,1852],{"class":372},[362,5477,4894],{"class":384},[362,5479,5480,5482],{"class":364,"line":376},[362,5481,4899],{"class":372},[362,5483,4902],{"class":384},[362,5485,5486,5488,5490,5492],{"class":364,"line":401},[362,5487,4907],{"class":372},[362,5489,385],{"class":384},[362,5491,876],{"class":368},[362,5493,4914],{"class":384},[362,5495,5496,5498,5500,5502,5504,5506,5508,5510,5512,5514,5516,5518,5520,5522],{"class":364,"line":419},[362,5497,4919],{"class":368},[362,5499,1863],{"class":384},[362,5501,3889],{"class":372},[362,5503,4926],{"class":384},[362,5505,4929],{"class":372},[362,5507,4932],{"class":384},[362,5509,876],{"class":368},[362,5511,4937],{"class":384},[362,5513,2351],{"class":368},[362,5515,4942],{"class":384},[362,5517,2830],{"class":368},[362,5519,4947],{"class":384},[362,5521,2351],{"class":368},[362,5523,4952],{"class":384},[362,5525,5526,5528,5530],{"class":364,"line":437},[362,5527,4957],{"class":384},[362,5529,736],{"class":368},[362,5531,4962],{"class":384},[362,5533,5534,5536,5538,5540],{"class":364,"line":444},[362,5535,4967],{"class":372},[362,5537,385],{"class":384},[362,5539,876],{"class":368},[362,5541,4914],{"class":384},[362,5543,5544],{"class":364,"line":453},[362,5545,3095],{"class":384},[10,5547,5548],{},"用只读模式打开OpenPropertyStore的时候，我们用PKEY_Device_FriendlyName取值放在pv里，然后赋值完后就清理。",[118,5550,5552],{"id":5551},"暴露给前端electron-n-api简述","暴露给前端（Electron \u002F N-API，简述）",[10,5554,5555,5560,5561,5567,5568,5573,5574,5579,5580,5584,5585,5590,5591,5596,5597,5601,5602,5607],{},[14,5556,5557],{},[21,5558,5559],{},"addon.cpp"," 里把 ",[14,5562,5563,151,5565],{},[21,5564,4220],{},[21,5566,4241],{}," 用 ",[14,5569,5570],{},[21,5571,5572],{},"WideCharToMultiByte(CP_UTF8, …)"," 填进 ",[14,5575,5576],{},[21,5577,5578],{},"{ id, name }"," 数组；Vue 里 ",[14,5581,5582],{},[21,5583,343],{}," 再 UTF-8 传回 ",[14,5586,5587],{},[21,5588,5589],{},"startRecording","，底层 ",[14,5592,5593],{},[21,5594,5595],{},"Utf8ToWide"," 后交给 ",[14,5598,5599],{},[21,5600,1756],{},"。链条与 ",[14,5603,5604],{},[21,5605,5606],{},"WGC捕获迁移到Electron.md"," 一致，此处不重复贴 JS。",[99,5609],{},[102,5611,5613],{"id":5612},"pcm-格式与-aac-入口和-loopback-对上","PCM 格式与 AAC 入口（和 loopback 对上）",[223,5615,5616,5641],{},[226,5617,5618,5623,5624,5629,5630,147,5636,240],{},[14,5619,5620],{},[21,5621,5622],{},"SetupAudioStreaming","：输出 ",[14,5625,5626],{},[21,5627,5628],{},"MFAudioFormat_AAC","，输入 ",[14,5631,5632,5635],{},[21,5633,5634],{},"WAVE_FORMAT_PCM"," 16-bit",[14,5637,5638],{},[21,5639,5640],{},"MFInitMediaTypeFromWaveFormatEx",[226,5642,5643,5644,5648,5649,5655,5656,5659,5660,5668,5669,5673,5674,5678],{},"Loopback ",[14,5645,5646],{},[21,5647,1750],{}," 常为 float：",[14,5650,5651,5652,5654],{},"必须在进入 ",[21,5653,173],{}," 之前"," 转成 ",[14,5657,5658],{},"PCM16","，并与 ",[14,5661,5662,151,5665],{},[21,5663,5664],{},"config.audioChannels",[21,5666,5667],{},"audioSampleRate"," 对齐（若 ",[14,5670,5671],{},[21,5672,96],{}," 里用 ",[14,5675,5676],{},[21,5677,1750],{}," 覆盖了配置，应以设备为准）。",[99,5680],{},[102,5682,5684,5685,5687],{"id":5683},"注册流与-beginwriting结构摘录","注册流与 ",[21,5686,2467],{},"（结构摘录）",[10,5689,5690,5623,5695,5629,5698,5701,5702,5705,5706,252],{},[14,5691,5692],{},[21,5693,5694],{},"SetupVideoStreaming",[14,5696,5697],{},"H.264",[14,5699,5700],{},"RGB32（BGRA）","，设置 ",[14,5703,5704],{},"bitrate、MF_MT_FRAME_SIZE、帧率、stride、PAR、nominal range"," 等，然后 ",[14,5707,5708,130,5711],{},[21,5709,5710],{},"AddStream",[21,5712,5713],{},"SetInputMediaType",[10,5715,5716,5623,5720,5629,5723,5726,5727,5732,5733,147,5736,5741,5742,252],{},[14,5717,5718],{},[21,5719,5622],{},[14,5721,5722],{},"AAC",[14,5724,5725],{},"PCM","；",[14,5728,5729],{},[21,5730,5731],{},"MF_MT_AUDIO_AVG_BYTES_PER_SECOND"," 在仓库里用 ",[14,5734,5735],{},"AAC 比特率换算成的字节\u002F秒",[14,5737,5738],{},[21,5739,5740],{},"128000\u002F8","），勿填 PCM 的 ",[14,5743,5744],{},[21,5745,5746],{},"nAvgBytesPerSec",[10,5748,5749,5753,5754,5758,5759,5764,5765,2668,5770,5775,5776,2668,5780,5784,5785,5789,5790,2668,5794,5798,5799,5802,5803,252,5808,5813,5814,39,5819,5822,5823,5828,5829,5833],{},[14,5750,5751],{},[21,5752,2485],{},"（顺序与 ",[14,5755,5756],{},[21,5757,54],{}," 一致）：",[14,5760,5761],{},[21,5762,5763],{},"m_useRgbMemoryPath = false","、查 ",[14,5766,5767],{},[21,5768,5769],{},"QPCFrequency",[14,5771,5772],{},[21,5773,5774],{},"MFCreateAttributes"," →（上文）",[14,5777,5778],{},[21,5779,2583],{},[14,5781,5782],{},[21,5783,5694],{}," → 若 ",[14,5786,5787],{},[21,5788,338],{}," 则 ",[14,5791,5792],{},[21,5793,5622],{},[14,5795,5796],{},[21,5797,2467],{}," → 创建 ",[14,5800,5801],{},"staging"," 纹理 → ",[14,5804,5805],{},[21,5806,5807],{},"m_isRecording = true",[14,5809,5810],{},[21,5811,5812],{},"StopRecording"," 会 ",[14,5815,5816],{},[21,5817,5818],{},"Finalize",[21,5820,5821],{},"Reset"," SinkWriter，并把 ",[14,5824,5825],{},[21,5826,5827],{},"m_useRgbMemoryPath"," 清回 ",[14,5830,5831],{},[21,5832,1059],{},"，下次录制重新尝试 DXGI 路径。",[99,5835],{},[102,5837,5839],{"id":5838},"mf-模块与历史表略作校正","MF 模块与历史表（略作校正）",[223,5841,5842,5850,5865],{},[226,5843,5844,5849],{},[14,5845,5846],{},[21,5847,5848],{},"mfplat.dll","：Buffer、Sample、属性袋等底座。",[226,5851,5852,2662,5857,5864],{},[14,5853,5854],{},[21,5855,5856],{},"mfreadwrite.dll",[14,5858,5859,151,5861],{},[21,5860,23],{},[21,5862,5863],{},"IMFSourceReader","，简化拓扑拼装。",[226,5866,5867,5870,5871,5873,5874,5878],{},[14,5868,5869],{},"MFT","：编码\u002F解码\u002F处理可由系统或显卡驱动注册；",[14,5872,2461],{}," 是否参与仍受 ",[14,5875,5876],{},[21,5877,48],{}," 与拓扑影响。",[10,5880,5881,5882,252],{},"操作系统支持表你原文可保留：",[14,5883,5884],{},"Vista 引入，Win7 强化 H.264 + SinkWriter，Win10\u002F11 现状标准",[99,5886],{},[102,5888,5890],{"id":5889},"与-wgc-winrt-的配合按需","与 WGC \u002F WinRT 的配合（按需）",[10,5892,5893,5898,5899,5904,5905,5910,5911,5916],{},[14,5894,5895],{},[21,5896,5897],{},"winrt::com_ptr"," 与 ",[14,5900,5901],{},[21,5902,5903],{},"WRL::ComPtr","：WGC 帧常落在 ",[14,5906,5907],{},[21,5908,5909],{},"winrt::com_ptr\u003CID3D11Texture2D>","，交给 MF 时 ",[14,5912,5913],{},[21,5914,5915],{},"texture.get()"," 取裸指针即可；不必强行纠结「现代化」与否，边界清晰就好。",[99,5918],{},[5920,5921,5922],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":358,"searchDepth":376,"depth":376,"links":5924},[5925,5929,5939,5947,5954,5955,5957,5958],{"id":104,"depth":376,"text":105,"children":5926},[5927,5928],{"id":120,"depth":401,"text":121},{"id":209,"depth":401,"text":210},{"id":264,"depth":376,"text":264,"children":5930},[5931,5933,5935,5937],{"id":267,"depth":401,"text":5932},"头文件与类（与当前 media_recorder.h 一致）",{"id":1697,"depth":401,"text":5934},"HRESULT 与 COM 成败判断",{"id":1726,"depth":401,"text":5936},"WasapiLoopback::Initialize：设备与 LOOPBACK",{"id":2260,"depth":401,"text":5938},"捕获线程：AvSetMmThreadCharacteristicsW、SILENT 位",{"id":2415,"depth":376,"text":5940,"children":5941},"MediaRecorder：两层「硬件不行就软件」——和仓库完全一致",[5942,5944,5945],{"id":2437,"depth":401,"text":5943},"第一层（编码拓扑）：MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS",{"id":2625,"depth":401,"text":2626},{"id":3662,"depth":401,"text":5946},"PushAudioData：与仓库一致的完整实现",{"id":4074,"depth":376,"text":5948,"children":5949},"播放设备「友好名称」与稳定 ID（audio_output_devices.cpp）",[5950,5952,5953],{"id":4110,"depth":401,"text":5951},"头文件：AudioOutputDeviceInfo",{"id":4257,"depth":401,"text":4258},{"id":5551,"depth":401,"text":5552},{"id":5612,"depth":376,"text":5613},{"id":5683,"depth":376,"text":5956},"注册流与 BeginWriting（结构摘录）",{"id":5838,"depth":376,"text":5839},{"id":5889,"depth":376,"text":5890},"在 Windows 平台上，Media Foundation（常口头叫 WMF\u002FMF） 是比 FFmpeg 更「原生」的一套管线：系统 DLL + 可选显卡驱动的 MFT（Media Foundation Transform）。你可以用 IMFSinkWriter 把视频轨、音频轨mux进 MP4。","md",{"date":5962,"tags":5963},"2026-10-17",[5964,5965,357],"media","wmf","\u002Fmedia\u002Fwmf完成高性能的音频处理",{"description":5959},"media\u002FWMF完成高性能的音频处理","V1fYEfe1KCQaFKU-n0BF1gqZzbXOxcO3QcfIdkMSerg",1792338206489]