Files
ppsspp/Windows/CaptureDevice.h
T
Henrik RydgårdandClaude Opus 5.5 3656fb46e2 Windows: Capture the microphone through WASAPI instead of Media Foundation
Windows now uses the same host interface as SDL and iOS: sceUsbMic sends
startRecording/stopRecording/deviceChanged, and drains buffered input on
the CPU thread with pollRecording. A capture thread asks WASAPI for mono
16-bit at the game's rate (it converts from the mix format, so no more
ffmpeg resampling), and copies packets into a lock-free ring buffer.
It reopens the device when the selection changes, and retries once a
second if the device is missing or gets unplugged.

The device list comes from the active capture endpoints. Their friendly
names are the ones Media Foundation reported, so existing MicDevice
settings still match. An empty setting, now selectable as Auto like on
SDL, means the default capture device.

The Media Foundation audio capture code is removed; the camera still
uses it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 18:28:33 +02:00

229 lines
6.4 KiB
C++

// Copyright (c) 2020- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <queue>
#include <thread>
#include <wrl/client.h>
#include "Common/CommonTypes.h"
#ifdef __cplusplus
extern "C" {
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#endif // __cplusplus
struct VideoFormatTransform {
GUID MFVideoFormat;
AVPixelFormat AVVideoFormat;
};
enum class CAPTUREDEVICE_TYPE {
VIDEO,
};
enum class CAPTUREDEVICE_STATE {
UNINITIALIZED,
LOST,
STOPPED,
STARTED,
SHUTDOWN
};
enum class CAPTUREDEVICE_COMMAND {
START,
STOP,
SHUTDOWN,
UPDATE_STATE
};
enum CAPTUREDEVICE_ERROR {
CAPTUREDEVIDE_ERROR_NO_ERROR,
CAPTUREDEVIDE_ERROR_UNKNOWN_TYPE = 0x80000001,
CAPTUREDEVIDE_ERROR_INIT_FAILED,
CAPTUREDEVIDE_ERROR_START_FAILED,
CAPTUREDEVIDE_ERROR_STOP_FAILED,
CAPTUREDEVIDE_ERROR_GETNAMES_FAILED
};
struct CAPTUREDEVICE_MESSAGE {
CAPTUREDEVICE_COMMAND command;
void *opacity;
};
struct ChooseDeviceParam {
IMFActivate **ppDevices;
UINT32 count;
UINT32 selection;
};
union MediaParam {
struct {
UINT32 width;
UINT32 height;
LONG default_stride;
GUID videoFormat;
};
};
template <class T> void SafeRelease(T **ppT) {
if (*ppT) {
(*ppT)->Release();
*ppT = nullptr;
}
}
class WindowsCaptureDevice;
class ReaderCallback final : public IMFSourceReaderCallback {
public:
ReaderCallback(WindowsCaptureDevice *device);
virtual ~ReaderCallback();
// IUnknown methods.
STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override;
STDMETHODIMP_(ULONG) AddRef() override { return 0; } // Unused, just define.
STDMETHODIMP_(ULONG) Release() override { return 0; } // Unused, just define.
// IMFSourceReaderCallback methods.
STDMETHODIMP OnReadSample(
HRESULT hrStatus,
DWORD dwStreamIndex,
DWORD dwStreamFlags,
LONGLONG llTimestamp,
IMFSample *pSample // Can be null,even if hrStatus is success.
) override;
STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override { return S_OK; }
STDMETHODIMP OnFlush(DWORD) override { return S_OK; }
AVPixelFormat getAVVideoFormatbyMFVideoFormat(const GUID &MFVideoFormat);
/*
* Always convert the image to RGB24
* @param dst/src pointer to destination/source image
* @param dstW/srcW, dstH/srcH destination/source image's width and height in pixels
* @param dstLineSizes get the linesize of each plane by av_image_fill_linesizes()
* @param srcFormat MF_MT_SUBTYPE attribute of source image
* @param srcPadding should be setted to non-zero if source image has padding
*/
void imgConvert(
unsigned char *dst, unsigned int &dstW, unsigned int &dstH, int dstLineSizes[4],
unsigned char *src, const unsigned int &srcW, const unsigned int &srcH, const GUID &srcFormat,
const int &srcPadding);
// Flip image start and end in memory, it is neccessary if stride of source image is a negative value
// Might need some tests in different machine.
void imgInvert(unsigned char *dst, unsigned char *src, const int &srcW, const int &srcH, const GUID &srcFormat, const int &srcStride);
void imgInvertRGBA(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h);
void imgInvertRGB(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h);
void imgInvertYUY2(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h);
void imgInvertNV12(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h);
protected:
WindowsCaptureDevice *device;
SwsContext *img_convert_ctx = nullptr;
};
class WindowsCaptureDevice {
public:
WindowsCaptureDevice(CAPTUREDEVICE_TYPE type);
~WindowsCaptureDevice();
void CheckDevices();
bool start(void *startParam);
bool stop();
CAPTUREDEVICE_ERROR getError() const { return error; }
std::string getErrorMessage() const { return errorMessage; }
int getDeviceCounts() const { return param.count; }
// Get a list contained friendly device name.
std::vector<std::string> getDeviceList(bool forceEnum = false, int *pActuallCount = nullptr);
void setError(const CAPTUREDEVICE_ERROR &newError, const std::string &newErrorMessage) { error = newError; errorMessage = newErrorMessage; }
void setSelection(const UINT32 &selection) { param.selection = selection; }
HRESULT setDeviceParam(IMFMediaType *pType);
bool isShutDown() const { return state == CAPTUREDEVICE_STATE::SHUTDOWN; }
bool isStarted() const { return state == CAPTUREDEVICE_STATE::STARTED; }
void waitShutDown();
void sendMessage(CAPTUREDEVICE_MESSAGE message);
CAPTUREDEVICE_MESSAGE getMessage();
HRESULT enumDevices();
friend class ReaderCallback;
protected:
void updateState(const CAPTUREDEVICE_STATE &newState);
// Handle message here.
void messageHandler();
CAPTUREDEVICE_TYPE type;
MediaParam deviceParam;
MediaParam targetMediaParam;
CAPTUREDEVICE_STATE state;
ChooseDeviceParam param;
CAPTUREDEVICE_ERROR error;
std::string errorMessage;
bool isDeviceChanged = false;
// MF interface.
Microsoft::WRL::ComPtr<ReaderCallback> m_pCallback;
Microsoft::WRL::ComPtr<IMFSourceReader> m_pReader;
Microsoft::WRL::ComPtr<IMFMediaSource> m_pSource;
// Message loop.
std::mutex mutex;
std::condition_variable cond;
std::queue<CAPTUREDEVICE_MESSAGE> messageQueue;
// For the shutdown event safety.
std::mutex sdMutex;
// Param updating synchronously.
std::mutex paramMutex;
std::mutex stateMutex_;
std::condition_variable stateCond_;
unsigned char *imageRGB = nullptr;
int imgRGBLineSizes[4]{};
unsigned char *imageJpeg = nullptr;
int imgJpegSize = 0;
std::thread thread_;
};
extern WindowsCaptureDevice *winCamera;
bool RegisterCMPTMFApis();
bool UnRegisterCMPTMFApis();