mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
[fix] fix restart audio capturer error on MacOSX
This commit is contained in:
@@ -2,11 +2,11 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||||
|
|
||||||
|
#include "rd_log.h"
|
||||||
#include "speaker_capturer_macosx.h"
|
#include "speaker_capturer_macosx.h"
|
||||||
|
|
||||||
// 这个delegate用来接收SCStream回调
|
|
||||||
@interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput>
|
@interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput>
|
||||||
@property(nonatomic, assign) SpeakerCapturerMacosx* owner; // assign用于C++指针,不用weak
|
@property(nonatomic, assign) SpeakerCapturerMacosx* owner;
|
||||||
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner;
|
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -27,8 +27,6 @@
|
|||||||
size_t length = CMBlockBufferGetDataLength(blockBuffer);
|
size_t length = CMBlockBufferGetDataLength(blockBuffer);
|
||||||
char* dataPtr = NULL;
|
char* dataPtr = NULL;
|
||||||
CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr);
|
CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr);
|
||||||
|
|
||||||
// 获取输入格式
|
|
||||||
CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
|
CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
|
||||||
const AudioStreamBasicDescription* asbd =
|
const AudioStreamBasicDescription* asbd =
|
||||||
CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
|
CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
|
||||||
@@ -46,7 +44,7 @@
|
|||||||
if (v < -1.0f) v = -1.0f;
|
if (v < -1.0f) v = -1.0f;
|
||||||
pcm16[i] = (short)(v * 32767.0f);
|
pcm16[i] = (short)(v * 32767.0f);
|
||||||
}
|
}
|
||||||
// 混合为单声道
|
|
||||||
if (channels > 1) {
|
if (channels > 1) {
|
||||||
int mono_samples = samples / channels;
|
int mono_samples = samples / channels;
|
||||||
out_pcm16.resize(mono_samples);
|
out_pcm16.resize(mono_samples);
|
||||||
@@ -79,28 +77,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分包,每960字节送一次cb_(即480采样点)
|
|
||||||
size_t frame_bytes = 960; // 480 * 2
|
size_t frame_bytes = 960; // 480 * 2
|
||||||
size_t total_bytes = out_pcm16.size() * sizeof(short);
|
size_t total_bytes = out_pcm16.size() * sizeof(short);
|
||||||
unsigned char* p = (unsigned char*)out_pcm16.data();
|
unsigned char* p = (unsigned char*)out_pcm16.data();
|
||||||
for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) {
|
for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) {
|
||||||
_owner->cb_(p + offset, frame_bytes, "audio");
|
_owner->cb_(p + offset, frame_bytes, "audio");
|
||||||
}
|
}
|
||||||
// 如有剩余,可缓存到下次补齐
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// C++类实现细节,放这里,隐藏在.mm中
|
|
||||||
class SpeakerCapturerMacosx::Impl {
|
class SpeakerCapturerMacosx::Impl {
|
||||||
public:
|
public:
|
||||||
SCStreamConfiguration* config = nil;
|
SCStreamConfiguration* config = nil;
|
||||||
SCStream* stream = nil;
|
SCStream* stream = nil;
|
||||||
SpeakerCaptureDelegate* delegate = nil;
|
SpeakerCaptureDelegate* delegate = nil;
|
||||||
|
dispatch_queue_t queue = nil;
|
||||||
|
SCShareableContent* content = nil;
|
||||||
|
SCDisplay* mainDisplay = nil;
|
||||||
|
|
||||||
|
~Impl() {
|
||||||
|
if (stream) {
|
||||||
|
[stream stopCaptureWithCompletionHandler:^(NSError* _Nullable error){
|
||||||
|
}];
|
||||||
|
stream = nil;
|
||||||
|
}
|
||||||
|
delegate = nil;
|
||||||
|
if (queue) {
|
||||||
|
queue = nil;
|
||||||
|
}
|
||||||
|
content = nil;
|
||||||
|
mainDisplay = nil;
|
||||||
|
config = nil;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SpeakerCapturerMacosx::SpeakerCapturerMacosx() { impl_ = new Impl(); }
|
SpeakerCapturerMacosx::SpeakerCapturerMacosx() {
|
||||||
|
impl_ = new Impl();
|
||||||
|
inited_ = false;
|
||||||
|
cb_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
SpeakerCapturerMacosx::~SpeakerCapturerMacosx() {
|
SpeakerCapturerMacosx::~SpeakerCapturerMacosx() {
|
||||||
Destroy();
|
Destroy();
|
||||||
@@ -109,7 +126,9 @@ SpeakerCapturerMacosx::~SpeakerCapturerMacosx() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int SpeakerCapturerMacosx::Init(speaker_data_cb cb) {
|
int SpeakerCapturerMacosx::Init(speaker_data_cb cb) {
|
||||||
if (inited_) return 0;
|
if (inited_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
cb_ = cb;
|
cb_ = cb;
|
||||||
|
|
||||||
impl_->config = [[SCStreamConfiguration alloc] init];
|
impl_->config = [[SCStreamConfiguration alloc] init];
|
||||||
@@ -117,94 +136,129 @@ int SpeakerCapturerMacosx::Init(speaker_data_cb cb) {
|
|||||||
impl_->config.sampleRate = 48000;
|
impl_->config.sampleRate = 48000;
|
||||||
impl_->config.channelCount = 1;
|
impl_->config.channelCount = 1;
|
||||||
|
|
||||||
impl_->delegate = [[SpeakerCaptureDelegate alloc] initWithOwner:this];
|
|
||||||
|
|
||||||
// 异步获取可共享内容,改为同步等待
|
|
||||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||||
__block SCShareableContent* content = nil;
|
|
||||||
__block NSError* error = nil;
|
__block NSError* error = nil;
|
||||||
[SCShareableContent
|
[SCShareableContent
|
||||||
getShareableContentWithCompletionHandler:^(SCShareableContent* c, NSError* e) {
|
getShareableContentWithCompletionHandler:^(SCShareableContent* c, NSError* e) {
|
||||||
content = c;
|
impl_->content = c;
|
||||||
error = e;
|
error = e;
|
||||||
dispatch_semaphore_signal(sema);
|
dispatch_semaphore_signal(sema);
|
||||||
}];
|
}];
|
||||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||||
|
|
||||||
if (error || !content) return -1;
|
if (error || !impl_->content) {
|
||||||
|
LOG_ERROR("Failed to get shareable content: {}",
|
||||||
|
std::string([error.localizedDescription UTF8String]));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// 选择主显示屏
|
|
||||||
CGDirectDisplayID mainDisplayId = CGMainDisplayID();
|
CGDirectDisplayID mainDisplayId = CGMainDisplayID();
|
||||||
SCDisplay* mainDisplay = nil;
|
impl_->mainDisplay = nil;
|
||||||
for (SCDisplay* d in content.displays) {
|
for (SCDisplay* d in impl_->content.displays) {
|
||||||
if (d.displayID == mainDisplayId) {
|
if (d.displayID == mainDisplayId) {
|
||||||
mainDisplay = d;
|
impl_->mainDisplay = d;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!mainDisplay) return -1;
|
if (!impl_->mainDisplay) {
|
||||||
|
LOG_ERROR("Main display not found");
|
||||||
// 用SCContentFilter包装
|
|
||||||
SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:mainDisplay
|
|
||||||
excludingWindows:@[]];
|
|
||||||
|
|
||||||
impl_->stream = [[SCStream alloc] initWithFilter:filter
|
|
||||||
configuration:impl_->config
|
|
||||||
delegate:impl_->delegate];
|
|
||||||
|
|
||||||
NSError* addOutputError = nil;
|
|
||||||
dispatch_queue_t queue = dispatch_queue_create("SpeakerAudio.Queue", DISPATCH_QUEUE_SERIAL);
|
|
||||||
BOOL ok = [impl_->stream addStreamOutput:impl_->delegate
|
|
||||||
type:SCStreamOutputTypeAudio
|
|
||||||
sampleHandlerQueue:queue
|
|
||||||
error:&addOutputError];
|
|
||||||
if (!ok || addOutputError) {
|
|
||||||
NSLog(@"addStreamOutput error: %@", addOutputError);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!impl_->queue) {
|
||||||
|
impl_->queue = dispatch_queue_create("SpeakerAudio.Queue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
}
|
||||||
|
|
||||||
inited_ = true;
|
inited_ = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpeakerCapturerMacosx::Start() {
|
int SpeakerCapturerMacosx::Start() {
|
||||||
if (!inited_) return -1;
|
if (!inited_) {
|
||||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (impl_->stream) {
|
||||||
|
dispatch_semaphore_t semaStop = dispatch_semaphore_create(0);
|
||||||
|
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
|
||||||
|
dispatch_semaphore_signal(semaStop);
|
||||||
|
}];
|
||||||
|
dispatch_semaphore_wait(semaStop, DISPATCH_TIME_FOREVER);
|
||||||
|
impl_->stream = nil;
|
||||||
|
impl_->delegate = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_->delegate = [[SpeakerCaptureDelegate alloc] initWithOwner:this];
|
||||||
|
SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:impl_->mainDisplay
|
||||||
|
excludingWindows:@[]];
|
||||||
|
impl_->stream = [[SCStream alloc] initWithFilter:filter
|
||||||
|
configuration:impl_->config
|
||||||
|
delegate:impl_->delegate];
|
||||||
|
|
||||||
|
NSError* addOutputError = nil;
|
||||||
|
BOOL ok = [impl_->stream addStreamOutput:impl_->delegate
|
||||||
|
type:SCStreamOutputTypeAudio
|
||||||
|
sampleHandlerQueue:impl_->queue
|
||||||
|
error:&addOutputError];
|
||||||
|
if (!ok || addOutputError) {
|
||||||
|
LOG_ERROR("addStreamOutput error: {}",
|
||||||
|
std::string([addOutputError.localizedDescription UTF8String]));
|
||||||
|
impl_->stream = nil;
|
||||||
|
impl_->delegate = nil;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_semaphore_t semaStart = dispatch_semaphore_create(0);
|
||||||
__block int ret = 0;
|
__block int ret = 0;
|
||||||
[impl_->stream startCaptureWithCompletionHandler:^(NSError* _Nullable error) {
|
[impl_->stream startCaptureWithCompletionHandler:^(NSError* _Nullable error) {
|
||||||
if (error) ret = -1;
|
if (error) {
|
||||||
dispatch_semaphore_signal(sema);
|
LOG_ERROR("startCaptureWithCompletionHandler error: {}",
|
||||||
|
std::string([error.localizedDescription UTF8String]));
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
dispatch_semaphore_signal(semaStart);
|
||||||
}];
|
}];
|
||||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
dispatch_semaphore_wait(semaStart, DISPATCH_TIME_FOREVER);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpeakerCapturerMacosx::Stop() {
|
int SpeakerCapturerMacosx::Stop() {
|
||||||
if (!inited_) return -1;
|
if (!inited_) return -1;
|
||||||
|
if (!impl_->stream) return -1;
|
||||||
|
|
||||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||||
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
|
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
|
||||||
|
if (error) {
|
||||||
|
LOG_ERROR("stopCaptureWithCompletionHandler error: {}",
|
||||||
|
std::string([error.localizedDescription UTF8String]));
|
||||||
|
}
|
||||||
dispatch_semaphore_signal(sema);
|
dispatch_semaphore_signal(sema);
|
||||||
}];
|
}];
|
||||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||||
inited_ = false;
|
|
||||||
|
impl_->stream = nil;
|
||||||
|
impl_->delegate = nil;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpeakerCapturerMacosx::Destroy() {
|
int SpeakerCapturerMacosx::Destroy() {
|
||||||
Stop();
|
Stop();
|
||||||
cb_ = nullptr;
|
cb_ = nullptr;
|
||||||
|
|
||||||
if (impl_) {
|
if (impl_) {
|
||||||
impl_->delegate = nil;
|
|
||||||
impl_->stream = nil;
|
|
||||||
impl_->config = nil;
|
impl_->config = nil;
|
||||||
|
impl_->content = nil;
|
||||||
|
impl_->mainDisplay = nil;
|
||||||
|
if (impl_->queue) {
|
||||||
|
impl_->queue = nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inited_ = false;
|
inited_ = false;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpeakerCapturerMacosx::Pause() {
|
int SpeakerCapturerMacosx::Pause() { return 0; }
|
||||||
// ScreenCaptureKit无暂停接口,暂时无实现
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SpeakerCapturerMacosx::Resume() { return Start(); }
|
int SpeakerCapturerMacosx::Resume() { return Start(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user