mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-06 18:40:37 +00:00
This commit introduces initial support for MacOS as third major host platform. It relies on the VideoToolbox framework for audio and video processing, which enables hardware accelerated processing of the stream on most platforms. Audio capturing requires third party tools as MacOS does not offer the recording of the audio output like the other platforms do. The commit enables most features offered by Sunshine for MacOS with the big exception of gamepad support. The patch sets was tested by a few volunteers, which allowed to remove some of the early bugs. However, several bugs especially regarding corner cases have probably not surfaced yet. Besides instructions how to build from source, the commit also adds a Portfile that allows a more easy installation. After available on the release branch, a pull request for the Portfile in the MacPorts project is planned. Signed-off-by: Anselm Busse <anselm.busse@outlook.com>
197 lines
6.1 KiB
Plaintext
197 lines
6.1 KiB
Plaintext
#include "sunshine/platform/common.h"
|
|
#include "sunshine/platform/macos/av_img_t.h"
|
|
#include "sunshine/platform/macos/av_video.h"
|
|
#include "sunshine/platform/macos/nv12_zero_device.h"
|
|
|
|
#include "sunshine/config.h"
|
|
#include "sunshine/main.h"
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace platf {
|
|
using namespace std::literals;
|
|
|
|
av_img_t::~av_img_t() {
|
|
if(pixel_buffer != NULL) {
|
|
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);
|
|
}
|
|
|
|
if(sample_buffer != nullptr) {
|
|
CFRelease(sample_buffer);
|
|
}
|
|
|
|
data = nullptr;
|
|
}
|
|
|
|
struct av_display_t : public display_t {
|
|
AVVideo *av_capture;
|
|
CGDirectDisplayID display_id;
|
|
|
|
~av_display_t() {
|
|
[av_capture release];
|
|
}
|
|
|
|
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
|
|
__block auto img_next = std::move(img);
|
|
|
|
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
|
|
auto av_img_next = std::static_pointer_cast<av_img_t>(img_next);
|
|
|
|
CFRetain(sampleBuffer);
|
|
|
|
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
|
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
|
|
|
if(av_img_next->pixel_buffer != nullptr)
|
|
CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0);
|
|
|
|
if(av_img_next->sample_buffer != nullptr)
|
|
CFRelease(av_img_next->sample_buffer);
|
|
|
|
av_img_next->sample_buffer = sampleBuffer;
|
|
av_img_next->pixel_buffer = pixelBuffer;
|
|
img_next->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer);
|
|
|
|
size_t extraPixels[4];
|
|
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
|
|
|
|
img_next->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
|
|
img_next->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
|
|
img_next->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
img_next->pixel_pitch = img_next->row_pitch / img_next->width;
|
|
|
|
img_next = snapshot_cb(img_next);
|
|
|
|
return img_next != nullptr;
|
|
}];
|
|
|
|
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
|
|
|
|
return capture_e::ok;
|
|
}
|
|
|
|
std::shared_ptr<img_t> alloc_img() override {
|
|
return std::make_shared<av_img_t>();
|
|
}
|
|
|
|
std::shared_ptr<hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override {
|
|
if(pix_fmt == pix_fmt_e::yuv420p) {
|
|
av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
|
|
|
|
return std::make_shared<hwdevice_t>();
|
|
}
|
|
else if(pix_fmt == pix_fmt_e::nv12) {
|
|
auto device = std::make_shared<nv12_zero_device>();
|
|
|
|
device->init(static_cast<void *>(av_capture), setResolution, setPixelFormat);
|
|
|
|
return device;
|
|
}
|
|
else {
|
|
BOOST_LOG(error) << "Unsupported Pixel Format."sv;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
int dummy_img(img_t *img) override {
|
|
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
|
|
auto av_img = (av_img_t *)img;
|
|
|
|
CFRetain(sampleBuffer);
|
|
|
|
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
|
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
|
|
|
// XXX: next_img->img should be moved to a smart pointer with
|
|
// the CFRelease as custom deallocator
|
|
if(av_img->pixel_buffer != nullptr)
|
|
CVPixelBufferUnlockBaseAddress(((av_img_t *)img)->pixel_buffer, 0);
|
|
|
|
if(av_img->sample_buffer != nullptr)
|
|
CFRelease(av_img->sample_buffer);
|
|
|
|
av_img->sample_buffer = sampleBuffer;
|
|
av_img->pixel_buffer = pixelBuffer;
|
|
img->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer);
|
|
|
|
size_t extraPixels[4];
|
|
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
|
|
|
|
img->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
|
|
img->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
|
|
img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
img->pixel_pitch = img->row_pitch / img->width;
|
|
|
|
return false;
|
|
}];
|
|
|
|
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* A bridge from the pure C++ code of the hwdevice_t class to the pure Objective C code.
|
|
*
|
|
* display --> an opaque pointer to an object of this class
|
|
* width --> the intended capture width
|
|
* height --> the intended capture height
|
|
*/
|
|
static void setResolution(void *display, int width, int height) {
|
|
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
|
|
}
|
|
|
|
static void setPixelFormat(void *display, OSType pixelFormat) {
|
|
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
|
|
}
|
|
};
|
|
|
|
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
|
if(hwdevice_type != platf::mem_type_e::system) {
|
|
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
|
return nullptr;
|
|
}
|
|
|
|
auto display = std::make_shared<av_display_t>();
|
|
|
|
display->display_id = CGMainDisplayID();
|
|
if(!display_name.empty()) {
|
|
auto display_array = [AVVideo displayNames];
|
|
|
|
for(NSDictionary *item in display_array) {
|
|
NSString *name = item[@"name"];
|
|
if(name.UTF8String == display_name) {
|
|
NSNumber *display_id = item[@"id"];
|
|
display->display_id = [display_id unsignedIntValue];
|
|
}
|
|
}
|
|
}
|
|
|
|
display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:framerate];
|
|
|
|
if(!display->av_capture) {
|
|
BOOST_LOG(error) << "Video setup failed."sv;
|
|
return nullptr;
|
|
}
|
|
|
|
display->width = display->av_capture.frameWidth;
|
|
display->height = display->av_capture.frameHeight;
|
|
|
|
return display;
|
|
}
|
|
|
|
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
|
__block std::vector<std::string> display_names;
|
|
|
|
auto display_array = [AVVideo displayNames];
|
|
|
|
display_names.reserve([display_array count]);
|
|
[display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
|
NSString *name = obj[@"name"];
|
|
display_names.push_back(name.UTF8String);
|
|
}];
|
|
|
|
return display_names;
|
|
}
|
|
}
|