Switched to libsoundio
This commit is contained in:
		
							parent
							
								
									7007e8f6aa
								
							
						
					
					
						commit
						411778f932
					
				| @ -3,6 +3,8 @@ window["require_setup"](module); | |||||||
| import {audio as naudio} from "teaclient_connection"; | import {audio as naudio} from "teaclient_connection"; | ||||||
| 
 | 
 | ||||||
| namespace audio.player  { | namespace audio.player  { | ||||||
|  |     //FIXME: Native audio initialize handle!
 | ||||||
|  | 
 | ||||||
|     export interface Device { |     export interface Device { | ||||||
|         device_id: string; |         device_id: string; | ||||||
|         name: string; |         name: string; | ||||||
| @ -15,6 +17,7 @@ namespace audio.player  { | |||||||
| 
 | 
 | ||||||
|     let _initialized_callbacks: (() => any)[] = []; |     let _initialized_callbacks: (() => any)[] = []; | ||||||
|     export let _initialized = false; |     export let _initialized = false; | ||||||
|  |     export let _initializing = false; | ||||||
|     export let _audioContext: AudioContext; |     export let _audioContext: AudioContext; | ||||||
|     export let _processor: ScriptProcessorNode; |     export let _processor: ScriptProcessorNode; | ||||||
|     export let _output_stream: naudio.playback.OwnedAudioOutputStream; |     export let _output_stream: naudio.playback.OwnedAudioOutputStream; | ||||||
| @ -44,6 +47,10 @@ namespace audio.player  { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     export function initialize() { |     export function initialize() { | ||||||
|  |         if(_initializing) return; | ||||||
|  |         _initializing = true; | ||||||
|  | 
 | ||||||
|  |         naudio.initialize(() => { | ||||||
|             _output_stream = naudio.playback.create_stream(); |             _output_stream = naudio.playback.create_stream(); | ||||||
|             _output_stream.set_buffer_max_latency(0.4); |             _output_stream.set_buffer_max_latency(0.4); | ||||||
|             _output_stream.set_buffer_latency(0.02); |             _output_stream.set_buffer_latency(0.02); | ||||||
| @ -77,26 +84,28 @@ namespace audio.player  { | |||||||
|             for(const callback of _initialized_callbacks) |             for(const callback of _initialized_callbacks) | ||||||
|                 callback(); |                 callback(); | ||||||
|             _initialized_callbacks = []; |             _initialized_callbacks = []; | ||||||
|  |         }); | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     export async function available_devices() : Promise<Device[]> { |     export async function available_devices() : Promise<Device[]> { | ||||||
|         return naudio.available_devices().filter(e => e.output_supported || e.output_default).map(e => { |         return naudio.available_devices().filter(e => e.output_supported || e.output_default); | ||||||
|             return { |  | ||||||
|                 device_id: e.device_id, |  | ||||||
|                 name: e.name |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     export async function set_device(device_id?: string) : Promise<void> { |     export async function set_device(device_id?: string) : Promise<void> { | ||||||
|         const dev = naudio.available_devices().filter(e => e.device_id == device_id); |         const dev = naudio.available_devices().filter(e => e.device_id == device_id); | ||||||
|         if(dev.length == 0) { |         if(dev.length == 0) { | ||||||
|             console.warn("Missing audio device with is %s", device_id) |             console.warn("Missing audio device with is %s", device_id); | ||||||
|             throw "invalid device id"; |             throw "invalid device id"; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         await naudio.playback.set_device(dev[0].device_index); |         try { | ||||||
|  |             naudio.playback.set_device(dev[0].device_id); | ||||||
|  |         } catch(error) { | ||||||
|  |             if(error instanceof Error) | ||||||
|  |                 throw error.message; | ||||||
|  |             throw error; | ||||||
|  |         } | ||||||
|         _current_device = dev[0]; |         _current_device = dev[0]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| window["require_setup"](module); | window["require_setup"](module); | ||||||
| 
 | 
 | ||||||
| import {audio as naudio} from "teaclient_connection"; | import {audio as naudio} from "teaclient_connection"; | ||||||
| //import {audio, tr} from "../imports/imports_shared";
 | // <reference types="../imports/import_shared.d.ts" />
 | ||||||
| /// <reference types="./imports/import_shared.d.ts" />
 | /// <reference types="../../modules/renderer/imports/imports_shared.d.ts" />
 | ||||||
| 
 | 
 | ||||||
| export namespace _audio.recorder { | export namespace _audio.recorder { | ||||||
|     import InputDevice = audio.recorder.InputDevice; |     import InputDevice = audio.recorder.InputDevice; | ||||||
| @ -14,6 +14,9 @@ export namespace _audio.recorder { | |||||||
| 
 | 
 | ||||||
|     let _device_cache: NativeDevice[] = undefined; |     let _device_cache: NativeDevice[] = undefined; | ||||||
|     export function devices() : InputDevice[] { |     export function devices() : InputDevice[] { | ||||||
|  |         //TODO: Handle device updates!
 | ||||||
|  |         if(!naudio.initialized()) return []; | ||||||
|  | 
 | ||||||
|         return _device_cache || (_device_cache = naudio.available_devices().filter(e => e.input_supported || e.input_default).map(e => { |         return _device_cache || (_device_cache = naudio.available_devices().filter(e => e.input_supported || e.input_default).map(e => { | ||||||
|             return { |             return { | ||||||
|                 unique_id: e.device_id, |                 unique_id: e.device_id, | ||||||
| @ -22,8 +25,7 @@ export namespace _audio.recorder { | |||||||
|                 supported: e.input_supported, |                 supported: e.input_supported, | ||||||
|                 name: e.name, |                 name: e.name, | ||||||
|                 driver: e.driver, |                 driver: e.driver, | ||||||
|                 sample_rate: 44100, /* TODO! */ |                 sample_rate: 48000, /* TODO! */ | ||||||
|                 device_index: e.device_index, |  | ||||||
|             } as NativeDevice |             } as NativeDevice | ||||||
|         })); |         })); | ||||||
|     } |     } | ||||||
| @ -293,22 +295,13 @@ export namespace _audio.recorder { | |||||||
|             const device = _device as NativeDevice; /* TODO: test for? */ |             const device = _device as NativeDevice; /* TODO: test for? */ | ||||||
|             this._current_device = _device; |             this._current_device = _device; | ||||||
|             try { |             try { | ||||||
|                 await new Promise((resolve, reject) => { |                 await new Promise(resolve => this.handle.set_device(this._current_device.unique_id, resolve)); | ||||||
|                     this.handle.set_device(device ? device.device_index : -1, flag => { |  | ||||||
|                         if(typeof(flag) === "boolean" && flag) |  | ||||||
|                             resolve(); |  | ||||||
|                         else |  | ||||||
|                             reject("failed to set device" + (typeof(flag) === "string" ? (": " + flag) : "")); |  | ||||||
|                     }); |  | ||||||
|                 }); |  | ||||||
|                 if(!device) return; |  | ||||||
| 
 |  | ||||||
|                 await new Promise((resolve, reject) => { |                 await new Promise((resolve, reject) => { | ||||||
|                     this.handle.start(flag => { |                     this.handle.start(flag => { | ||||||
|                         if(flag) |                         if(typeof flag === "boolean" && flag) | ||||||
|                             resolve(); |                             resolve(); | ||||||
|                         else |                         else | ||||||
|                             reject("start failed"); |                             reject(typeof flag === "string" ? flag : "failed to start"); | ||||||
|                     }); |                     }); | ||||||
|                 }); |                 }); | ||||||
|             } catch(error) { |             } catch(error) { | ||||||
| @ -453,20 +446,13 @@ export namespace _audio.recorder { | |||||||
|                 this._filter.set_attack_smooth(.75); |                 this._filter.set_attack_smooth(.75); | ||||||
|                 this._filter.set_release_smooth(.75); |                 this._filter.set_release_smooth(.75); | ||||||
| 
 | 
 | ||||||
|                 await new Promise((resolve, reject) => { |                 await new Promise(resolve => this._recorder.set_device(this._device.unique_id, resolve)); | ||||||
|                     this._recorder.set_device(this._device.device_index, flag => { |  | ||||||
|                         if(typeof(flag) === "boolean" && flag) |  | ||||||
|                             resolve(); |  | ||||||
|                         else |  | ||||||
|                             reject("initialize failed" + (typeof(flag) === "string" ? (": " + flag) : "")); |  | ||||||
|                     }); |  | ||||||
|                 }); |  | ||||||
|                 await new Promise((resolve, reject) => { |                 await new Promise((resolve, reject) => { | ||||||
|                     this._recorder.start(flag => { |                     this._recorder.start(flag => { | ||||||
|                         if(flag) |                         if(typeof flag === "boolean" && flag) | ||||||
|                             resolve(); |                             resolve(); | ||||||
|                         else |                         else | ||||||
|                             reject("start failed"); |                             reject(typeof flag === "string" ? flag : "failed to start"); | ||||||
|                     }); |                     }); | ||||||
|                 }); |                 }); | ||||||
|             } catch(error) { |             } catch(error) { | ||||||
| @ -490,7 +476,7 @@ export namespace _audio.recorder { | |||||||
|             if(this._consumer) |             if(this._consumer) | ||||||
|                 this._recorder.delete_consumer(this._consumer); |                 this._recorder.delete_consumer(this._consumer); | ||||||
|             this._recorder.stop(); |             this._recorder.stop(); | ||||||
|             this._recorder.set_device(-1, () => {}); /* -1 := No device */ |             this._recorder.set_device(undefined, () => {}); /* -1 := No device */ | ||||||
|             this._recorder = undefined; |             this._recorder = undefined; | ||||||
|             this._consumer = undefined; |             this._consumer = undefined; | ||||||
|             this._filter = undefined; |             this._filter = undefined; | ||||||
| @ -507,4 +493,3 @@ export namespace _audio.recorder { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Object.assign(window["audio"] || (window["audio"] = {} as any), _audio); | Object.assign(window["audio"] || (window["audio"] = {} as any), _audio); | ||||||
| _audio.recorder.devices(); /* query devices */ |  | ||||||
| @ -84,7 +84,7 @@ namespace Nan { | |||||||
| 		struct callback_wrap { | 		struct callback_wrap { | ||||||
| 			std::shared_ptr<callback_scoped<Args...>> handle; | 			std::shared_ptr<callback_scoped<Args...>> handle; | ||||||
| 
 | 
 | ||||||
| 			void call_cpy(Args... args, bool no_throw = false) { | 			void call_cpy(Args... args, bool no_throw = false) const { | ||||||
| 				if(!this->handle) { | 				if(!this->handle) { | ||||||
| 					if(no_throw) | 					if(no_throw) | ||||||
| 						return; | 						return; | ||||||
| @ -93,7 +93,7 @@ namespace Nan { | |||||||
| 				handle->callback(std::forward<Args>(args)...); | 				handle->callback(std::forward<Args>(args)...); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			void call(Args&&... args, bool no_throw = false) { | 			void call(Args&&... args, bool no_throw = false) const { | ||||||
| 				if(!this->handle) { | 				if(!this->handle) { | ||||||
| 					if(no_throw) | 					if(no_throw) | ||||||
| 						return; | 						return; | ||||||
| @ -102,11 +102,6 @@ namespace Nan { | |||||||
| 				handle->callback(std::forward<Args>(args)...); | 				handle->callback(std::forward<Args>(args)...); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			void operator()(Args&&... args) { |  | ||||||
| 				if(!this->handle) |  | ||||||
| 					throw std::bad_function_call(); |  | ||||||
| 				handle->callback(std::forward<Args>(args)...); |  | ||||||
| 			} |  | ||||||
| 			void operator()(Args&&... args) const { | 			void operator()(Args&&... args) const { | ||||||
| 				if(!this->handle) | 				if(!this->handle) | ||||||
| 					throw std::bad_function_call(); | 					throw std::bad_function_call(); | ||||||
|  | |||||||
| @ -5,12 +5,12 @@ set(SOURCE_FILES | |||||||
| 		src/logger.cpp | 		src/logger.cpp | ||||||
| 		src/EventLoop.cpp | 		src/EventLoop.cpp | ||||||
| 		src/hwuid.cpp | 		src/hwuid.cpp | ||||||
|  |         src/ring_buffer.cpp | ||||||
| 
 | 
 | ||||||
| 		src/connection/ft/FileTransferManager.cpp | 		src/connection/ft/FileTransferManager.cpp | ||||||
| 		src/connection/ft/FileTransferObject.cpp | 		src/connection/ft/FileTransferObject.cpp | ||||||
| 
 | 
 | ||||||
| 		src/audio/AudioSamples.cpp | 		src/audio/AudioSamples.cpp | ||||||
| 		src/audio/AudioDevice.cpp |  | ||||||
| 		src/audio/AudioMerger.cpp | 		src/audio/AudioMerger.cpp | ||||||
| 		src/audio/AudioOutput.cpp | 		src/audio/AudioOutput.cpp | ||||||
| 		src/audio/AudioInput.cpp | 		src/audio/AudioInput.cpp | ||||||
| @ -23,6 +23,11 @@ set(SOURCE_FILES | |||||||
| 
 | 
 | ||||||
| 		src/audio/codec/Converter.cpp | 		src/audio/codec/Converter.cpp | ||||||
| 		src/audio/codec/OpusConverter.cpp | 		src/audio/codec/OpusConverter.cpp | ||||||
|  | 
 | ||||||
|  |         src/audio/driver/AudioDriver.cpp | ||||||
|  |         src/audio/driver/SoundIO.cpp | ||||||
|  |         src/audio/driver/SoundIOPlayback.cpp | ||||||
|  |         src/audio/driver/SoundIORecord.cpp | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| set(NODEJS_SOURCE_FILES | set(NODEJS_SOURCE_FILES | ||||||
| @ -83,6 +88,8 @@ include_directories(${StringVariable_INCLUDE_DIR}) | |||||||
| find_package(Ed25519 REQUIRED) | find_package(Ed25519 REQUIRED) | ||||||
| include_directories(${ed25519_INCLUDE_DIR}) | include_directories(${ed25519_INCLUDE_DIR}) | ||||||
| 
 | 
 | ||||||
|  | find_package(soundio REQUIRED) | ||||||
|  | 
 | ||||||
| find_package(ThreadPool REQUIRED) | find_package(ThreadPool REQUIRED) | ||||||
| include_directories(${ThreadPool_INCLUDE_DIR}) | include_directories(${ThreadPool_INCLUDE_DIR}) | ||||||
| if (WIN32) | if (WIN32) | ||||||
| @ -95,9 +102,6 @@ endif () | |||||||
| find_package(Soxr REQUIRED) | find_package(Soxr REQUIRED) | ||||||
| include_directories(${soxr_INCLUDE_DIR}) | include_directories(${soxr_INCLUDE_DIR}) | ||||||
| 
 | 
 | ||||||
| find_package(PortAudio REQUIRED) |  | ||||||
| include_directories(${PortAudio_INCLUDE_DIR}) |  | ||||||
| 
 |  | ||||||
| find_package(fvad REQUIRED) | find_package(fvad REQUIRED) | ||||||
| include_directories(${fvad_INCLUDE_DIR}) | include_directories(${fvad_INCLUDE_DIR}) | ||||||
| 
 | 
 | ||||||
| @ -118,11 +122,11 @@ set(REQUIRED_LIBRARIES | |||||||
| 	${DataPipes_LIBRARIES_STATIC} #Needs to be static because something causes ca bad function call when loaded in electron | 	${DataPipes_LIBRARIES_STATIC} #Needs to be static because something causes ca bad function call when loaded in electron | ||||||
| 	${ThreadPool_LIBRARIES_STATIC} | 	${ThreadPool_LIBRARIES_STATIC} | ||||||
| 	${soxr_LIBRARIES_STATIC} | 	${soxr_LIBRARIES_STATIC} | ||||||
| 	${PortAudio_LIBRARIES_STATIC} |  | ||||||
| 	${fvad_LIBRARIES_STATIC} | 	${fvad_LIBRARIES_STATIC} | ||||||
| 	${opus_LIBRARIES_STATIC} | 	${opus_LIBRARIES_STATIC} | ||||||
| 
 | 
 | ||||||
| 	${ed25519_LIBRARIES_STATIC} | 	${ed25519_LIBRARIES_STATIC} | ||||||
|  |     soundio::static | ||||||
| 
 | 
 | ||||||
| 	spdlog::spdlog_header_only | 	spdlog::spdlog_header_only | ||||||
| 	Nan::Helpers | 	Nan::Helpers | ||||||
| @ -144,10 +148,10 @@ target_link_libraries(${MODULE_NAME} ${REQUIRED_LIBRARIES}) | |||||||
| target_compile_definitions(${MODULE_NAME} PUBLIC -DNODEJS_API) | target_compile_definitions(${MODULE_NAME} PUBLIC -DNODEJS_API) | ||||||
| 
 | 
 | ||||||
| add_executable(Audio-Test ${SOURCE_FILES} test/audio/main.cpp) | add_executable(Audio-Test ${SOURCE_FILES} test/audio/main.cpp) | ||||||
| target_link_libraries(Audio-Test ${REQUIRED_LIBRARIES}) | target_link_libraries(Audio-Test ${REQUIRED_LIBRARIES} soundio.a) | ||||||
| 
 | 
 | ||||||
| add_executable(Audio-Test-2 ${SOURCE_FILES} test/audio/sio.cpp) | add_executable(Audio-Test-2 ${SOURCE_FILES} test/audio/sio.cpp) | ||||||
| target_link_libraries(Audio-Test-2 ${REQUIRED_LIBRARIES} soundio.a pulse) | target_link_libraries(Audio-Test-2 ${REQUIRED_LIBRARIES} soundio.a) | ||||||
| 
 | 
 | ||||||
| add_executable(HW-UID-Test src/hwuid.cpp) | add_executable(HW-UID-Test src/hwuid.cpp) | ||||||
| target_link_libraries(HW-UID-Test | target_link_libraries(HW-UID-Test | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								native/serverconnection/exports/exports.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								native/serverconnection/exports/exports.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -144,8 +144,6 @@ declare module "teaclient_connection" { | |||||||
| 
 | 
 | ||||||
|             input_default: boolean; |             input_default: boolean; | ||||||
|             output_default: boolean; |             output_default: boolean; | ||||||
| 
 |  | ||||||
|             device_index: number; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         export namespace playback { |         export namespace playback { | ||||||
| @ -174,8 +172,8 @@ declare module "teaclient_connection" { | |||||||
|                 delete(); |                 delete(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             export function set_device(device: number); |             export function set_device(device_id: string); | ||||||
|             export function current_device() : number; |             export function current_device() : string; | ||||||
| 
 | 
 | ||||||
|             export function create_stream() : OwnedAudioOutputStream; |             export function create_stream() : OwnedAudioOutputStream; | ||||||
| 
 | 
 | ||||||
| @ -234,10 +232,10 @@ declare module "teaclient_connection" { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             export interface AudioRecorder { |             export interface AudioRecorder { | ||||||
|                 get_device() : number; |                 get_device() : string; | ||||||
|                 set_device(device: number, callback: (flag: boolean | string) => void); /* Recorder needs to be started afterwards */ |                 set_device(device_id: string, callback: () => void); /* Recorder needs to be started afterwards */ | ||||||
| 
 | 
 | ||||||
|                 start(callback: (flag: boolean) => void); |                 start(callback: (result: boolean | string) => void); | ||||||
|                 started() : boolean; |                 started() : boolean; | ||||||
|                 stop(); |                 stop(); | ||||||
| 
 | 
 | ||||||
| @ -252,7 +250,8 @@ declare module "teaclient_connection" { | |||||||
|             export function create_recorder() : AudioRecorder; |             export function create_recorder() : AudioRecorder; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         export function initialize(); |         export function initialize(callback: () => any); | ||||||
|  |         export function initialized() : boolean; | ||||||
|         export function available_devices() : AudioDevice[]; |         export function available_devices() : AudioDevice[]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -1,102 +0,0 @@ | |||||||
| #include "AudioDevice.h" |  | ||||||
| #include "../logger.h" |  | ||||||
| 
 |  | ||||||
| using namespace std; |  | ||||||
| using namespace tc; |  | ||||||
| using namespace tc::audio; |  | ||||||
| 
 |  | ||||||
| extern bool devices_cached(); /* if the result is false then the call to devices() may take a while */ |  | ||||||
| extern std::deque<std::shared_ptr<AudioDevice>> devices(); |  | ||||||
| 
 |  | ||||||
| bool _devices_cached = false; |  | ||||||
| std::mutex _audio_devices_lock; |  | ||||||
| std::deque<std::shared_ptr<AudioDevice>> _audio_devices{}; |  | ||||||
| 
 |  | ||||||
| bool audio::devices_cached() { |  | ||||||
| 	return _devices_cached; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void audio::clear_device_cache() { |  | ||||||
|     std::lock_guard lock(_audio_devices_lock); |  | ||||||
| 	_audio_devices.clear(); |  | ||||||
| 	_devices_cached = false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| deque<shared_ptr<AudioDevice>> audio::devices() { |  | ||||||
|     std::lock_guard lock(_audio_devices_lock); |  | ||||||
| 	if(_devices_cached) |  | ||||||
| 		return _audio_devices; |  | ||||||
| 
 |  | ||||||
| 	/* query devices */ |  | ||||||
| 	auto device_count = Pa_GetDeviceCount(); |  | ||||||
| 	if(device_count < 0) { |  | ||||||
| 		log_error(category::audio, tr("Pa_GetDeviceCount() returned {}"), device_count); |  | ||||||
| 		return {}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	auto default_input_device = Pa_GetDefaultInputDevice(); |  | ||||||
| 	auto default_output_device = Pa_GetDefaultOutputDevice(); |  | ||||||
| 
 |  | ||||||
| 	for(PaDeviceIndex device_index = 0; device_index < device_count; device_index++) { |  | ||||||
| 		auto device_info = Pa_GetDeviceInfo(device_index); |  | ||||||
| 		if(!device_info) { |  | ||||||
| 			log_warn(category::audio, tr("Pa_GetDeviceInfo(...) failed for device {}"), device_index); |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		auto device_host_info = Pa_GetHostApiInfo(device_info->hostApi); |  | ||||||
| 		if(!device_host_info) { |  | ||||||
| 			log_warn(category::audio, tr("Pa_GetHostApiInfo(...) failed for device {} with host api {}"), device_index, device_info->hostApi); |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		auto info = make_shared<AudioDevice>(); |  | ||||||
| 		info->device_id = device_index; |  | ||||||
| 		info->name = device_info->name; |  | ||||||
| 
 |  | ||||||
| 		info->max_inputs = device_info->maxInputChannels; |  | ||||||
| 		info->input_supported = device_info->maxInputChannels > 0; |  | ||||||
| 
 |  | ||||||
| 		info->max_outputs = device_info->maxOutputChannels; |  | ||||||
| 		info->output_supported = device_info->maxOutputChannels > 0; |  | ||||||
| 
 |  | ||||||
| 		info->is_default_input = device_index == default_input_device; |  | ||||||
| 		info->is_default_output = device_index == default_output_device; |  | ||||||
| 
 |  | ||||||
| 		info->driver = device_host_info->name; |  | ||||||
| 		info->is_default_driver_input = device_index == device_host_info->defaultInputDevice; |  | ||||||
| 		info->is_default_driver_output = device_index == device_host_info->defaultOutputDevice; |  | ||||||
| 
 |  | ||||||
| 		PaStreamParameters test_parameters{}; |  | ||||||
| 		test_parameters.device = device_index; |  | ||||||
| 		test_parameters.sampleFormat = paFloat32; |  | ||||||
| 		test_parameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ |  | ||||||
| 		test_parameters.hostApiSpecificStreamInfo = nullptr; |  | ||||||
| 
 |  | ||||||
| 		/*
 |  | ||||||
| 		if(info->input_supported) { |  | ||||||
| 			test_parameters.channelCount = device_info->maxInputChannels; |  | ||||||
| 			for(size_t index = 0; standard_sample_rates[index] > 0; index++) { |  | ||||||
| 				auto rate = standard_sample_rates[index]; |  | ||||||
| 				auto err = Pa_IsFormatSupported(&test_parameters, nullptr, rate); |  | ||||||
| 				if(err == paFormatIsSupported) |  | ||||||
| 					info->supported_input_rates.push_back(rate); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if(info->output_supported) { |  | ||||||
| 			test_parameters.channelCount = device_info->maxOutputChannels; |  | ||||||
| 			for(size_t index = 0; standard_sample_rates[index] > 0; index++) { |  | ||||||
| 				auto rate = standard_sample_rates[index]; |  | ||||||
| 				auto err = Pa_IsFormatSupported(nullptr, &test_parameters, rate); |  | ||||||
| 				if(err == paFormatIsSupported) |  | ||||||
| 					info->supported_output_rates.push_back(rate); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		 */ |  | ||||||
| 
 |  | ||||||
| 		_audio_devices.push_back(info); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_devices_cached = true; |  | ||||||
| 	return _audio_devices; |  | ||||||
| } |  | ||||||
| @ -1,44 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <mutex> |  | ||||||
| #include <deque> |  | ||||||
| #include <memory> |  | ||||||
| #include <iostream> |  | ||||||
| #include <functional> |  | ||||||
| #include <portaudio.h> |  | ||||||
| 
 |  | ||||||
| namespace tc { |  | ||||||
| 	namespace audio { |  | ||||||
| 		static constexpr double standard_sample_rates[] = { |  | ||||||
| 				8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, |  | ||||||
| 				44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated  list */ |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		struct AudioDevice { |  | ||||||
| 			PaDeviceIndex device_id; |  | ||||||
| 
 |  | ||||||
| 			bool is_default_output; |  | ||||||
| 			bool is_default_input; |  | ||||||
| 
 |  | ||||||
| 			bool is_default_driver_output; |  | ||||||
| 			bool is_default_driver_input; |  | ||||||
| 
 |  | ||||||
| 			bool input_supported; |  | ||||||
| 			bool output_supported; |  | ||||||
| 			std::string name; |  | ||||||
| 			std::string driver; |  | ||||||
| 
 |  | ||||||
| 			/*
 |  | ||||||
| 			std::vector<double> supported_input_rates; |  | ||||||
| 			std::vector<double> supported_output_rates; |  | ||||||
| 			*/ |  | ||||||
| 
 |  | ||||||
| 			int max_inputs; |  | ||||||
| 			int max_outputs; |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		extern void clear_device_cache(); |  | ||||||
| 		extern bool devices_cached(); /* if the result is false then the call to devices() may take a while */ |  | ||||||
| 		extern std::deque<std::shared_ptr<AudioDevice>> devices(); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @ -9,6 +9,7 @@ using namespace std; | |||||||
| using namespace tc; | using namespace tc; | ||||||
| using namespace tc::audio; | using namespace tc::audio; | ||||||
| 
 | 
 | ||||||
|  | #if false | ||||||
| class AudioInputSource { | class AudioInputSource { | ||||||
|     public: |     public: | ||||||
|         constexpr static auto kChannelCount{2}; |         constexpr static auto kChannelCount{2}; | ||||||
| @ -121,7 +122,7 @@ class AudioInputSource { | |||||||
| 
 | 
 | ||||||
|             std::lock_guard lock{input_source->registered_inputs_lock}; |             std::lock_guard lock{input_source->registered_inputs_lock}; | ||||||
|             for(auto& client : input_source->registered_inputs) |             for(auto& client : input_source->registered_inputs) | ||||||
|                 client->audio_callback(input, frameCount, timeInfo, statusFlags); |                 client->consume(input, frameCount, 2); | ||||||
|             return 0; |             return 0; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -152,6 +153,7 @@ std::shared_ptr<AudioInputSource> get_input_source(PaDeviceIndex device_index, b | |||||||
|     input_sources.push_back(std::make_shared<AudioInputSource>(device_index)); |     input_sources.push_back(std::make_shared<AudioInputSource>(device_index)); | ||||||
|     return input; |     return input; | ||||||
| } | } | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| AudioConsumer::AudioConsumer(tc::audio::AudioInput *handle, size_t channel_count, size_t sample_rate, size_t frame_size) : | AudioConsumer::AudioConsumer(tc::audio::AudioInput *handle, size_t channel_count, size_t sample_rate, size_t frame_size) : | ||||||
| 	handle(handle), | 	handle(handle), | ||||||
| @ -189,50 +191,57 @@ AudioInput::~AudioInput() { | |||||||
| 		consumer->handle = nullptr; | 		consumer->handle = nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| PaDeviceIndex AudioInput::current_device() { | void AudioInput::set_device(const std::shared_ptr<AudioDevice> &device) { | ||||||
|     lock_guard lock(this->input_source_lock); |     lock_guard lock(this->input_source_lock); | ||||||
|     return this->input_source ? this->input_source->device_index : paNoDevice; |     if(device == this->input_device) return; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool AudioInput::open_device(std::string& error, PaDeviceIndex index) { |  | ||||||
| 	lock_guard lock(this->input_source_lock); |  | ||||||
| 
 |  | ||||||
| 	if(index == (this->input_source ? this->input_source->device_index : paNoDevice)) |  | ||||||
| 		return true; |  | ||||||
| 
 | 
 | ||||||
|     this->close_device(); |     this->close_device(); | ||||||
| 	if(index == paNoDevice) |     this->input_device = device; | ||||||
| 		return true; |  | ||||||
| 
 |  | ||||||
| 	this->input_source = get_input_source(index, true); |  | ||||||
| 	this->input_source->register_consumer(this); |  | ||||||
| 	return this->input_source->begin_recording(error); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AudioInput::close_device() { | void AudioInput::close_device() { | ||||||
|     lock_guard lock(this->input_source_lock); |     lock_guard lock(this->input_source_lock); | ||||||
|     if(this->input_source) { |     if(this->input_recorder) { | ||||||
|         this->input_source->remove_consumer(this); |         this->input_recorder->remove_consumer(this); | ||||||
|         this->input_source->stop_recording_if_possible(); |         this->input_recorder->stop_if_possible(); | ||||||
|         this->input_source.reset(); |         this->input_recorder.reset(); | ||||||
|     } |     } | ||||||
|     this->input_recording = false; |     this->input_device = nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AudioInput::record() { | bool AudioInput::record(std::string& error) { | ||||||
|     lock_guard lock(this->input_source_lock); |     lock_guard lock(this->input_source_lock); | ||||||
|     if(!this->input_source) return false; |     if(!this->input_device) { | ||||||
|  |         error = "no device"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if(this->input_recorder) return true; | ||||||
| 
 | 
 | ||||||
|     this->input_recording = true; |     this->input_recorder = this->input_device->record(); | ||||||
|  |     if(!this->input_recorder) { | ||||||
|  |         error = "failed to get recorder"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->input_recorder->register_consumer(this); | ||||||
|  |     if(!this->input_recorder->start(error)) { | ||||||
|  |         this->input_recorder->remove_consumer(this); | ||||||
|  |         this->input_recorder.reset(); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AudioInput::recording() { | bool AudioInput::recording() { | ||||||
|     return this->input_recording; |     return !!this->input_recorder; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AudioInput::stop() { | void AudioInput::stop() { | ||||||
|     this->input_recording = false; |     if(!this->input_recorder) return; | ||||||
|  | 
 | ||||||
|  |     this->input_recorder->remove_consumer(this); | ||||||
|  |     this->input_recorder->stop_if_possible(); | ||||||
|  |     this->input_recorder.reset(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::shared_ptr<AudioConsumer> AudioInput::create_consumer(size_t frame_length) { | std::shared_ptr<AudioConsumer> AudioInput::create_consumer(size_t frame_length) { | ||||||
| @ -255,9 +264,7 @@ void AudioInput::delete_consumer(const std::shared_ptr<AudioConsumer> &source) { | |||||||
| 	source->handle = nullptr; | 	source->handle = nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AudioInput::audio_callback(const void *input, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) { | void AudioInput::consume(const void *input, unsigned long frameCount, size_t /* channels */) { | ||||||
|     if(!this->input_recording) return; |  | ||||||
| 
 |  | ||||||
| 	if(this->_volume != 1 && false) { | 	if(this->_volume != 1 && false) { | ||||||
| 		auto ptr = (float*) input; | 		auto ptr = (float*) input; | ||||||
| 		auto left = frameCount * this->_channel_count; | 		auto left = frameCount * this->_channel_count; | ||||||
|  | |||||||
| @ -5,9 +5,9 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <portaudio.h> |  | ||||||
| #include <misc/spin_lock.h> | #include <misc/spin_lock.h> | ||||||
| #include "AudioSamples.h" | #include "AudioSamples.h" | ||||||
|  | #include "driver/AudioDriver.h" | ||||||
| 
 | 
 | ||||||
| class AudioInputSource; | class AudioInputSource; | ||||||
| namespace tc { | namespace tc { | ||||||
| @ -31,25 +31,22 @@ namespace tc { | |||||||
| 				AudioConsumer(AudioInput* handle, size_t channel_count, size_t sample_rate, size_t frame_size); | 				AudioConsumer(AudioInput* handle, size_t channel_count, size_t sample_rate, size_t frame_size); | ||||||
| 
 | 
 | ||||||
| 				std::unique_ptr<Reframer> reframer; | 				std::unique_ptr<Reframer> reframer; | ||||||
| 				std::mutex buffer_lock; |  | ||||||
| 				size_t buffered_samples = 0; |  | ||||||
| 				std::deque<std::shared_ptr<SampleBuffer>> sample_buffers; |  | ||||||
| 
 | 
 | ||||||
| 				void process_data(const void* /* buffer */, size_t /* samples */); | 				void process_data(const void* /* buffer */, size_t /* samples */); | ||||||
| 				void handle_framed_data(const void* /* buffer */, size_t /* samples */); | 				void handle_framed_data(const void* /* buffer */, size_t /* samples */); | ||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
| 		class AudioInput { | 	    class AudioInput : public AudioDeviceRecord::Consumer { | ||||||
| 		        friend class ::AudioInputSource; | 		        friend class ::AudioInputSource; | ||||||
| 			public: | 			public: | ||||||
| 				AudioInput(size_t /* channels */, size_t /* rate */); | 				AudioInput(size_t /* channels */, size_t /* rate */); | ||||||
| 				virtual ~AudioInput(); | 				virtual ~AudioInput(); | ||||||
| 
 | 
 | ||||||
|                 [[nodiscard]] bool open_device(std::string& /* error */, PaDeviceIndex); |                 void set_device(const std::shared_ptr<AudioDevice>& /* device */); | ||||||
|                 [[nodiscard]] PaDeviceIndex current_device(); |                 [[nodiscard]] std::shared_ptr<AudioDevice> current_device() const { return this->input_device; } | ||||||
|                 void close_device(); |                 void close_device(); | ||||||
| 
 | 
 | ||||||
|                 [[nodiscard]] bool record(); |                 [[nodiscard]] bool record(std::string& /* error */); | ||||||
|                 [[nodiscard]] bool recording(); |                 [[nodiscard]] bool recording(); | ||||||
| 				void stop(); | 				void stop(); | ||||||
| 
 | 
 | ||||||
| @ -67,7 +64,7 @@ namespace tc { | |||||||
| 				inline float volume() { return this->_volume; } | 				inline float volume() { return this->_volume; } | ||||||
| 				inline void set_volume(float value) { this->_volume = value; } | 				inline void set_volume(float value) { this->_volume = value; } | ||||||
| 			private: | 			private: | ||||||
| 				void audio_callback(const void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags); | 				void consume(const void *, unsigned long, size_t) override; | ||||||
| 
 | 
 | ||||||
| 				size_t const _channel_count; | 				size_t const _channel_count; | ||||||
| 				size_t const _sample_rate; | 				size_t const _sample_rate; | ||||||
| @ -76,10 +73,10 @@ namespace tc { | |||||||
| 				std::deque<std::shared_ptr<AudioConsumer>> _consumers; | 				std::deque<std::shared_ptr<AudioConsumer>> _consumers; | ||||||
| 
 | 
 | ||||||
| 				std::recursive_mutex input_source_lock; | 				std::recursive_mutex input_source_lock; | ||||||
| 				bool input_recording{false}; |                 std::shared_ptr<AudioDevice> input_device{}; | ||||||
| 				std::shared_ptr<::AudioInputSource> input_source{}; |  | ||||||
| 
 |  | ||||||
| 				float _volume = 1.f; | 				float _volume = 1.f; | ||||||
|  | 
 | ||||||
|  |                 std::shared_ptr<AudioDeviceRecord> input_recorder{}; | ||||||
|         }; |         }; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -10,110 +10,127 @@ using namespace tc; | |||||||
| using namespace tc::audio; | using namespace tc::audio; | ||||||
| 
 | 
 | ||||||
| void AudioOutputSource::clear() { | void AudioOutputSource::clear() { | ||||||
| 	lock_guard lock(this->buffer_lock); |     this->buffer.clear(); | ||||||
| 	this->sample_buffers.clear(); |  | ||||||
| 	this->buffered_samples = 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) { |  | ||||||
| 	auto sample_count = samples; |  | ||||||
| 
 |  | ||||||
| 	_retest: |  | ||||||
| 	{ |  | ||||||
| 		lock_guard lock(this->buffer_lock); |  | ||||||
| 		if(this->buffering) { |  | ||||||
| 			if(this->buffered_samples > this->min_buffer) { |  | ||||||
| 				this->buffering = false; |  | ||||||
| 			} else { |  | ||||||
| 				return 0; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		while(sample_count > 0 && !this->sample_buffers.empty()) { |  | ||||||
| 			auto buf = this->sample_buffers[0]; |  | ||||||
| 			auto sc = min((size_t) (buf->sample_size - buf->sample_index), (size_t) sample_count); |  | ||||||
| 			if(sc > 0 && buffer) { /* just to ensure */ |  | ||||||
| 				memcpy(buffer, (char *) buf->sample_data + this->channel_count * buf->sample_index * 4, sc * this->channel_count * 4); |  | ||||||
| 			} else { |  | ||||||
| #ifndef WIN32 |  | ||||||
| 			    /* for my debugger */ |  | ||||||
| 			    __asm__("nop"); |  | ||||||
| #endif |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			sample_count -= sc; |  | ||||||
| 			buf->sample_index += (uint16_t) sc; |  | ||||||
| 			if(buf->sample_index == buf->sample_size) |  | ||||||
| 				this->sample_buffers.pop_front(); |  | ||||||
| 
 |  | ||||||
| 			if(buffer) |  | ||||||
| 				buffer = (char*) buffer + sc * this->channel_count * 4; |  | ||||||
| 		} |  | ||||||
| 		this->buffered_samples -= samples - sample_count; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if(sample_count > 0) { |  | ||||||
| 		if(this->on_underflow) { |  | ||||||
| 			if(this->on_underflow()) { |  | ||||||
| 				goto _retest; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
|     this->buffering = true; |     this->buffering = true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) { | ||||||
|  |     size_t written{0}, written_bytes{0}; | ||||||
|  | 
 | ||||||
|  |     load_buffer: | ||||||
|  |     auto available_bytes = this->buffer.fill_count(); | ||||||
|  |     if(available_bytes < sizeof(float) * this->channel_count) return written; | ||||||
|  | 
 | ||||||
|  |     auto available_samples = available_bytes / sizeof(float) / this->channel_count; | ||||||
|  |     //log_trace(category::audio, tr("Min: {}, Max: {}, Current: {}, Buffering: {}"), this->min_buffered_samples, this->max_buffered_samples, available_samples, this->buffering);
 | ||||||
|  |     if(this->buffering && available_samples < this->min_buffered_samples) return -2; | ||||||
|  | 
 | ||||||
|  |     this->buffering = false; | ||||||
|  |     if(available_samples >= samples - written) { | ||||||
|  |         const auto byte_length = (samples - written) * sizeof(float) * this->channel_count; | ||||||
|  |         memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length); | ||||||
|  |         this->buffer.advance_read_ptr(byte_length); | ||||||
|  |         return samples; | ||||||
|  |     } else { | ||||||
|  |         const auto byte_length = available_samples * sizeof(float) * this->channel_count; | ||||||
|  |         memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length); | ||||||
|  |         this->buffer.advance_read_ptr(byte_length); | ||||||
|  |         written += available_samples; | ||||||
|  |         written_bytes += byte_length; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     if(auto fn = this->on_underflow; fn) | ||||||
|  |         if(fn()) | ||||||
|  |             goto load_buffer; | ||||||
|  | 
 | ||||||
|  |     this->buffering = true; | ||||||
| 	if(this->on_read) | 	if(this->on_read) | ||||||
| 		this->on_read(); | 		this->on_read(); | ||||||
| 
 | 
 | ||||||
| 	return samples - sample_count; /* return the written samples */ | 	return written; /* return the written samples */ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) { | ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) { | ||||||
| 	auto buf = SampleBuffer::allocate((uint8_t) this->channel_count, (uint16_t) samples); |     size_t enqueued{0}; | ||||||
| 	if(!buf) |  | ||||||
| 		return -1; |  | ||||||
| 
 | 
 | ||||||
| 	buf->sample_index = 0; |     auto free_bytes = this->buffer.free_count(); | ||||||
| 	memcpy(buf->sample_data, buffer, this->channel_count * samples * 4); |     auto free_samples = free_bytes / sizeof(float) / this->channel_count; | ||||||
|  |     if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples; | ||||||
| 
 | 
 | ||||||
| 	return this->enqueue_samples(buf); |     if(free_samples >= samples) { | ||||||
|  |         const auto byte_length = samples * sizeof(float) * this->channel_count; | ||||||
|  |         memcpy(this->buffer.write_ptr(), buffer, byte_length); | ||||||
|  |         this->buffer.advance_write_ptr(byte_length); | ||||||
|  |         return samples; | ||||||
|  |     } else { | ||||||
|  |         const auto byte_length = free_samples * sizeof(float) * this->channel_count; | ||||||
|  |         memcpy(this->buffer.write_ptr(), buffer, byte_length); | ||||||
|  |         this->buffer.advance_write_ptr(byte_length); | ||||||
|  |         enqueued += free_samples; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| ssize_t AudioOutputSource::enqueue_samples(const std::shared_ptr<tc::audio::SampleBuffer> &buf) { |     if(auto fn = this->on_overflow; fn) | ||||||
| 	if(!buf) return 0; |         fn(samples - enqueued); | ||||||
| 
 |  | ||||||
| 	{ |  | ||||||
| 		unique_lock lock(this->buffer_lock); |  | ||||||
| 		if(this->max_latency > 0 && this->buffered_samples + buf->sample_size > this->max_latency) { |  | ||||||
| 			/* overflow! */ |  | ||||||
| 			auto overflow_length = this->buffered_samples + buf->sample_size - this->max_latency; |  | ||||||
| 			if(this->on_overflow) { |  | ||||||
| 				lock.unlock(); |  | ||||||
| 				this->on_overflow(overflow_length); |  | ||||||
| 				lock.lock(); |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
|     switch (this->overflow_strategy) { |     switch (this->overflow_strategy) { | ||||||
|         case overflow_strategy::discard_input: |         case overflow_strategy::discard_input: | ||||||
|             return -2; |             return -2; | ||||||
|         case overflow_strategy::discard_buffer_all: |         case overflow_strategy::discard_buffer_all: | ||||||
| 					this->sample_buffers.clear(); |             this->buffer.clear(); | ||||||
|             break; |             break; | ||||||
|         case overflow_strategy::discard_buffer_half: |         case overflow_strategy::discard_buffer_half: | ||||||
| 					this->sample_buffers.erase(this->sample_buffers.begin(), this->sample_buffers.begin() + (int) ceil(this->sample_buffers.size() / 2)); |             this->buffer.advance_read_ptr(this->buffer.fill_count() / 2); | ||||||
|             break; |             break; | ||||||
|         case overflow_strategy::ignore: |         case overflow_strategy::ignore: | ||||||
|             break; |             break; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  | 	return enqueued; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 		this->sample_buffers.push_back(buf); | ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *buffer, size_t samples) { | ||||||
| 		this->buffered_samples += buf->sample_size; |     auto free_bytes = this->buffer.free_count(); | ||||||
|  |     auto free_samples = free_bytes / sizeof(float) / this->channel_count; | ||||||
|  |     if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples; | ||||||
|  | 
 | ||||||
|  |     auto samples_to_write{samples}; | ||||||
|  |     if(samples_to_write > free_samples) samples_to_write = free_samples; | ||||||
|  |     const auto enqueued{samples_to_write}; | ||||||
|  |     { | ||||||
|  |         auto src_buffer = (const float*) buffer; | ||||||
|  |         auto target_buffer = (float*) this->buffer.write_ptr(); | ||||||
|  | 
 | ||||||
|  |         while (samples_to_write-- > 0) { | ||||||
|  |             *target_buffer = *src_buffer; | ||||||
|  |             *(target_buffer + 1) = *(src_buffer + samples); | ||||||
|  | 
 | ||||||
|  |             target_buffer += 2; | ||||||
|  |             src_buffer++; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 	return buf->sample_size; |     if(auto fn = this->on_overflow; fn) | ||||||
|  |         fn(samples - enqueued); | ||||||
|  | 
 | ||||||
|  |     switch (this->overflow_strategy) { | ||||||
|  |         case overflow_strategy::discard_input: | ||||||
|  |             return -2; | ||||||
|  |         case overflow_strategy::discard_buffer_all: | ||||||
|  |             this->buffer.clear(); | ||||||
|  |             break; | ||||||
|  |         case overflow_strategy::discard_buffer_half: | ||||||
|  |             this->buffer.advance_read_ptr(this->buffer.fill_count() / 2); | ||||||
|  |             break; | ||||||
|  |         case overflow_strategy::ignore: | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return enqueued; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) { } | AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) { } | ||||||
|  | 
 | ||||||
| AudioOutput::~AudioOutput() { | AudioOutput::~AudioOutput() { | ||||||
| 	this->close_device(); | 	this->close_device(); | ||||||
| 	this->cleanup_buffers(); | 	this->cleanup_buffers(); | ||||||
| @ -152,30 +169,32 @@ void AudioOutput::cleanup_buffers() { | |||||||
| 	this->source_buffer_length = 0; | 	this->source_buffer_length = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int AudioOutput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) { | void AudioOutput::fill_buffer(void *output, unsigned long frameCount, size_t channels) { | ||||||
| 	return reinterpret_cast<AudioOutput*>(_ptr_audio_output)->audio_callback(a, b, c, d, e); | 	lock_guard buffer_lock(this->buffer_lock); | ||||||
|  |     if(this->_volume <= 0) { | ||||||
|  |         for(auto& source : this->_sources) | ||||||
|  |             source->pop_samples(nullptr, frameCount); | ||||||
|  |         memset(output, 0, sizeof(frameCount) * channels * sizeof(float)); | ||||||
|  |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| int AudioOutput::audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) { |  | ||||||
| 	if(!output)  /* hmmm.. suspicious */ |  | ||||||
| 		return 0; |  | ||||||
| 
 |  | ||||||
| 	lock_guard buffer_lock(this->buffer_lock); |  | ||||||
| 	size_t buffer_length = frameCount * 4 * this->_channel_count; | 	size_t buffer_length = frameCount * 4 * this->_channel_count; | ||||||
| 	size_t sources = 0; | 	size_t sources = 0; | ||||||
| 	size_t actual_sources = 0; | 	size_t actual_sources = 0; | ||||||
| 
 | 
 | ||||||
| 	auto volume = this->_volume; |  | ||||||
| 	{ | 	{ | ||||||
| 		lock_guard lock(this->sources_lock); | 		lock_guard lock(this->sources_lock); | ||||||
| 		sources = this->_sources.size(); | 		sources = this->_sources.size(); | ||||||
| 		actual_sources = sources; | 		actual_sources = sources; | ||||||
| 
 | 
 | ||||||
| 		if(sources > 0) { | 		if(sources > 0) { | ||||||
| 			if(volume > 0) { /* allocate the required space */ | 			 /* allocate the required space */ | ||||||
|             auto source_buffer_length = buffer_length * sources; |             auto source_buffer_length = buffer_length * sources; | ||||||
|             auto source_merge_buffer_length = sizeof(void*) * sources; |             auto source_merge_buffer_length = sizeof(void*) * sources; | ||||||
| 
 | 
 | ||||||
|  |             //TODO: Move this out of the loop?
 | ||||||
|  |             { | ||||||
|  | 
 | ||||||
|                 if(this->source_buffer_length < source_buffer_length || !this->source_buffer) { |                 if(this->source_buffer_length < source_buffer_length || !this->source_buffer) { | ||||||
|                     if(this->source_buffer) |                     if(this->source_buffer) | ||||||
|                         free(this->source_buffer); |                         free(this->source_buffer); | ||||||
| @ -193,11 +212,10 @@ int AudioOutput::audio_callback(const void *input, void *output, unsigned long f | |||||||
| 			for(size_t index = 0; index < sources; index++) { | 			for(size_t index = 0; index < sources; index++) { | ||||||
| 				auto& source = this->_sources[index]; | 				auto& source = this->_sources[index]; | ||||||
| 
 | 
 | ||||||
| 				if(volume > 0) { |  | ||||||
|                 this->source_merge_buffer[index] = (char*) this->source_buffer + (buffer_length * index); |                 this->source_merge_buffer[index] = (char*) this->source_buffer + (buffer_length * index); | ||||||
|                 auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], frameCount); |                 auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], frameCount); | ||||||
|                 if(written_frames != frameCount) { |                 if(written_frames != frameCount) { | ||||||
| 						if(written_frames == 0) { |                     if(written_frames <= 0) { | ||||||
|                         this->source_merge_buffer[index] = nullptr; |                         this->source_merge_buffer[index] = nullptr; | ||||||
|                         actual_sources--; |                         actual_sources--; | ||||||
|                     } else { |                     } else { | ||||||
| @ -206,82 +224,60 @@ int AudioOutput::audio_callback(const void *input, void *output, unsigned long f | |||||||
|                         memset((char*) this->source_merge_buffer[index] + written, 0, (frameCount - written_frames) * this->_channel_count * 4); |                         memset((char*) this->source_merge_buffer[index] + written, 0, (frameCount - written_frames) * this->_channel_count * 4); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 				} else { |  | ||||||
| 					this->_sources[index]->pop_samples(nullptr, frameCount); |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(actual_sources > 0 && volume > 0) { | 	if(actual_sources > 0) { | ||||||
| 		if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount)) { | 		if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount)) | ||||||
| 			log_warn(category::audio, tr("failed to merge buffers!")); | 			log_warn(category::audio, tr("failed to merge buffers!")); | ||||||
| 		} | 
 | ||||||
|  | 		auto volume = this->_volume; | ||||||
|  | 		if(volume != 1) { | ||||||
|             auto float_length = this->_channel_count * frameCount; |             auto float_length = this->_channel_count * frameCount; | ||||||
|             auto data = (float*) output; |             auto data = (float*) output; | ||||||
|             while(float_length-- > 0) |             while(float_length-- > 0) | ||||||
|                 *data++ *= volume; |                 *data++ *= volume; | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 	} else { | 	} else { | ||||||
| 		memset(output, 0, this->_channel_count * 4 * frameCount); | 		memset(output, 0, this->_channel_count * sizeof(float) * frameCount); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 	return 0; | void AudioOutput::set_device(const std::shared_ptr<AudioDevice> &device) { | ||||||
| } |     lock_guard lock(this->device_lock); | ||||||
| 
 |     if(this->device == device) return; | ||||||
| bool AudioOutput::open_device(std::string& error, PaDeviceIndex index) { |  | ||||||
| 	lock_guard lock(this->output_stream_lock); |  | ||||||
| 
 |  | ||||||
| 	if(index == this->_current_device_index) |  | ||||||
| 		return true; |  | ||||||
| 
 | 
 | ||||||
|     this->close_device(); |     this->close_device(); | ||||||
| 	this->_current_device_index = index; |     this->device = device; | ||||||
| 	this->_current_device = Pa_GetDeviceInfo(index); |  | ||||||
| 
 |  | ||||||
| 	if(!this->_current_device) { |  | ||||||
| 		this->_current_device_index = paNoDevice; |  | ||||||
| 		error = "failed to get device info"; |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	PaStreamParameters output_parameters{}; |  | ||||||
| 	memset(&output_parameters, 0, sizeof(output_parameters)); |  | ||||||
| 	output_parameters.channelCount = (int) this->_channel_count; |  | ||||||
| 	output_parameters.device = this->_current_device_index; |  | ||||||
| 	output_parameters.sampleFormat = paFloat32; |  | ||||||
| 	output_parameters.suggestedLatency = this->_current_device->defaultLowOutputLatency; |  | ||||||
| 
 |  | ||||||
| 	auto err = Pa_OpenStream(&output_stream, nullptr, &output_parameters, (double) this->_sample_rate, paFramesPerBufferUnspecified, paClipOff, &AudioOutput::_audio_callback, this); |  | ||||||
| 	if(err != paNoError) { |  | ||||||
| 		error = to_string(err) + "/" + Pa_GetErrorText(err); |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool AudioOutput::playback() { |  | ||||||
| 	lock_guard lock(this->output_stream_lock); |  | ||||||
| 	if(!this->output_stream) |  | ||||||
| 		return false; |  | ||||||
| 
 |  | ||||||
| 	auto err = Pa_StartStream(this->output_stream); |  | ||||||
| 	if(err != paNoError && err != paStreamIsNotStopped) { |  | ||||||
| 		log_error(category::audio, tr("Pa_StartStream returned {}"), err); |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AudioOutput::close_device() { | void AudioOutput::close_device() { | ||||||
| 	lock_guard lock(this->output_stream_lock); |     lock_guard lock(this->device_lock); | ||||||
| 	if(this->output_stream) { |     if(this->_playback) { | ||||||
| 		/* TODO: Test for errors */ |         this->_playback->remove_source(this); | ||||||
| 		Pa_StopStream(this->output_stream); |         this->_playback->stop_if_possible(); | ||||||
| 		Pa_CloseStream(this->output_stream); |         this->_playback.reset(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 		this->output_stream = nullptr; |     this->device = nullptr; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool AudioOutput::playback(std::string& error) { | ||||||
|  | 	lock_guard lock(this->device_lock); | ||||||
|  | 	if(!this->device) { | ||||||
|  | 	    error = "invalid device handle"; | ||||||
|  |         return false; | ||||||
|  | 	} | ||||||
|  |     if(this->_playback) return true; | ||||||
|  | 
 | ||||||
|  | 	this->_playback = this->device->playback(); | ||||||
|  | 	if(!this->_playback) { | ||||||
|  | 	    error = "failed to allocate memory"; | ||||||
|  |         return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	this->_playback->register_source(this); | ||||||
|  |     return this->_playback->start(error); | ||||||
| } | } | ||||||
| @ -5,15 +5,15 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <portaudio.h> | #include "./AudioSamples.h" | ||||||
| #include "AudioSamples.h" | #include "./driver/AudioDriver.h" | ||||||
|  | #include "../ring_buffer.h" | ||||||
| 
 | 
 | ||||||
| #ifdef WIN32 | #ifdef WIN32 | ||||||
|     #define ssize_t int64_t |     #define ssize_t int64_t | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace tc { | namespace tc::audio { | ||||||
| 	namespace audio { |  | ||||||
| 		class AudioOutput; | 		class AudioOutput; | ||||||
| 
 | 
 | ||||||
| 		namespace overflow_strategy { | 		namespace overflow_strategy { | ||||||
| @ -32,13 +32,21 @@ namespace tc { | |||||||
| 				size_t const channel_count = 0; | 				size_t const channel_count = 0; | ||||||
| 				size_t const sample_rate = 0; | 				size_t const sample_rate = 0; | ||||||
| 
 | 
 | ||||||
| 				bool buffering = true; | 				[[nodiscard]] inline size_t max_supported_latency() const { | ||||||
| 				size_t min_buffer = 0; |                     return this->buffer.capacity() / this->channel_count / sizeof(float); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  |                 [[nodiscard]] inline size_t max_latency() const { | ||||||
|  | 				    const auto max_samples = this->max_supported_latency(); | ||||||
|  | 				    if(this->max_buffered_samples && this->max_buffered_samples <= max_samples) return this->max_buffered_samples; | ||||||
|  | 
 | ||||||
|  |                     return max_samples; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  | 				bool buffering{true}; | ||||||
|  | 				size_t min_buffered_samples{0}; | ||||||
|  |                 size_t max_buffered_samples{0}; | ||||||
| 
 | 
 | ||||||
| 				/* For stream set it to the max latency in samples you want.
 |  | ||||||
| 				 * Zero means unlimited |  | ||||||
| 				 */ |  | ||||||
| 				size_t max_latency = 0; |  | ||||||
| 				overflow_strategy::value overflow_strategy = overflow_strategy::discard_buffer_half; | 				overflow_strategy::value overflow_strategy = overflow_strategy::discard_buffer_half; | ||||||
| 
 | 
 | ||||||
| 				/* if it returns true then the it means that the buffer has been refilled, we have to test again */ | 				/* if it returns true then the it means that the buffer has been refilled, we have to test again */ | ||||||
| @ -49,24 +57,24 @@ namespace tc { | |||||||
| 				void clear(); | 				void clear(); | ||||||
| 				ssize_t pop_samples(void* /* output buffer */, size_t /* sample count */); | 				ssize_t pop_samples(void* /* output buffer */, size_t /* sample count */); | ||||||
| 				ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */); | 				ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */); | ||||||
| 				ssize_t enqueue_samples(const std::shared_ptr<SampleBuffer>& /* buffer */); | 				ssize_t enqueue_samples_no_interleave(const void * /* input buffer */, size_t /* sample count */); | ||||||
| 			private: | 			private: | ||||||
| 				AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate) : handle(handle), channel_count(channel_count), sample_rate(sample_rate) {} | 				AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate) : | ||||||
|  | 				    handle(handle), channel_count(channel_count), sample_rate(sample_rate), buffer{channel_count * sample_rate * sizeof(float)} { | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 				std::mutex buffer_lock; | 				tc::ring_buffer buffer; | ||||||
| 				size_t buffered_samples = 0; |  | ||||||
| 				std::deque<std::shared_ptr<SampleBuffer>> sample_buffers; |  | ||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
| 		class AudioOutput { |         class AudioOutput : public AudioDevicePlayback::Source { | ||||||
| 			public: | 			public: | ||||||
| 				AudioOutput(size_t /* channels */, size_t /* rate */); | 				AudioOutput(size_t /* channels */, size_t /* rate */); | ||||||
| 				virtual ~AudioOutput(); | 				virtual ~AudioOutput(); | ||||||
| 
 | 
 | ||||||
| 				bool open_device(std::string& /* error */, PaDeviceIndex); |                 void set_device(const std::shared_ptr<AudioDevice>& /* device */); | ||||||
| 				bool playback(); | 				bool playback(std::string& /* error */); | ||||||
| 				void close_device(); | 				void close_device(); | ||||||
| 				PaDeviceIndex current_device() { return this->_current_device_index; } |                 std::shared_ptr<AudioDevice> current_device() { return this->device; } | ||||||
| 
 | 
 | ||||||
| 				std::deque<std::shared_ptr<AudioOutputSource>> sources() { | 				std::deque<std::shared_ptr<AudioOutputSource>> sources() { | ||||||
| 					std::lock_guard lock(this->sources_lock); | 					std::lock_guard lock(this->sources_lock); | ||||||
| @ -82,8 +90,7 @@ namespace tc { | |||||||
| 				inline float volume() { return this->_volume; } | 				inline float volume() { return this->_volume; } | ||||||
| 				inline void set_volume(float value) { this->_volume = value; } | 				inline void set_volume(float value) { this->_volume = value; } | ||||||
| 			private: | 			private: | ||||||
| 				static int _audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*); | 				void fill_buffer(void *, size_t frames, size_t channels) override ; | ||||||
| 				int audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags); |  | ||||||
| 
 | 
 | ||||||
| 				size_t const _channel_count; | 				size_t const _channel_count; | ||||||
| 				size_t const _sample_rate; | 				size_t const _sample_rate; | ||||||
| @ -91,10 +98,9 @@ namespace tc { | |||||||
| 				std::mutex sources_lock; | 				std::mutex sources_lock; | ||||||
| 				std::deque<std::shared_ptr<AudioOutputSource>> _sources; | 				std::deque<std::shared_ptr<AudioOutputSource>> _sources; | ||||||
| 
 | 
 | ||||||
| 				std::recursive_mutex output_stream_lock; | 				std::recursive_mutex device_lock; | ||||||
| 				const PaDeviceInfo* _current_device = nullptr; |                 std::shared_ptr<AudioDevice> device{nullptr}; | ||||||
| 				PaDeviceIndex _current_device_index = paNoDevice; |                 std::shared_ptr<AudioDevicePlayback> _playback{nullptr}; | ||||||
| 				PaStream* output_stream = nullptr; |  | ||||||
| 
 | 
 | ||||||
| 				std::mutex buffer_lock; /* not required, but why not. Usually only used within audio_callback! */ | 				std::mutex buffer_lock; /* not required, but why not. Usually only used within audio_callback! */ | ||||||
| 				void* source_buffer = nullptr; | 				void* source_buffer = nullptr; | ||||||
| @ -107,4 +113,3 @@ namespace tc { | |||||||
| 				float _volume = 1.f; | 				float _volume = 1.f; | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| } |  | ||||||
							
								
								
									
										219
									
								
								native/serverconnection/src/audio/driver/AudioDriver.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								native/serverconnection/src/audio/driver/AudioDriver.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,219 @@ | |||||||
|  | //
 | ||||||
|  | // Created by wolverindev on 07.02.20.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include <thread> | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <ThreadPool/ThreadHelper.h> | ||||||
|  | #include "AudioDriver.h" | ||||||
|  | #include "SoundIO.h" | ||||||
|  | #include "../../logger.h" | ||||||
|  | #include "../AudioMerger.h" | ||||||
|  | 
 | ||||||
|  | using namespace tc::audio; | ||||||
|  | 
 | ||||||
|  | namespace tc::audio { | ||||||
|  |     std::deque<std::shared_ptr<AudioDevice>> devices() { | ||||||
|  |         std::deque<std::shared_ptr<AudioDevice>> result{}; | ||||||
|  |         for(auto& backend : SoundIOBackendHandler::all_backends()) { | ||||||
|  |             auto input_devices = backend->input_devices(); | ||||||
|  |             auto output_devices = backend->output_devices(); | ||||||
|  | 
 | ||||||
|  |             result.insert(result.end(), input_devices.begin(), input_devices.end()); | ||||||
|  |             result.insert(result.end(), output_devices.begin(), output_devices.end()); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::shared_ptr<AudioDevice> find_device_by_id(const std::string_view& id, bool input) { | ||||||
|  |         for(auto& backend : SoundIOBackendHandler::all_backends()) { | ||||||
|  |             for(auto& dev : input ? backend->input_devices() : backend->output_devices()) | ||||||
|  |                 if(dev->id() == id) | ||||||
|  |                     return dev; | ||||||
|  |         } | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::mutex initialize_lock{}; | ||||||
|  |     std::deque<initialize_callback_t> initialize_callbacks{}; | ||||||
|  |     int initialize_state{0}; /* 0 := not initialized | 1 := initialized | 2 := initializing */ | ||||||
|  | 
 | ||||||
|  |     void _initialize() { | ||||||
|  |         SoundIOBackendHandler::initialize_all(); | ||||||
|  |         SoundIOBackendHandler::connect_all(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void _finalize() { | ||||||
|  |         SoundIOBackendHandler::shutdown_all(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void initialize(const initialize_callback_t& callback) { | ||||||
|  |         { | ||||||
|  |             std::unique_lock init_lock{initialize_lock}; | ||||||
|  |             if(initialize_state == 2) { | ||||||
|  |                 if(callback) | ||||||
|  |                     initialize_callbacks.push_back(callback); | ||||||
|  |                 return; | ||||||
|  |             } else if(initialize_state == 1) { | ||||||
|  |                 init_lock.unlock(); | ||||||
|  |                 callback(); | ||||||
|  |                 return; | ||||||
|  |             } else if(initialize_state != 0) { | ||||||
|  |                 init_lock.unlock(); | ||||||
|  |                 callback(); | ||||||
|  |                 log_warn(category::audio, tr("Invalid initialize state ({})"), initialize_state); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             initialize_state = 2; | ||||||
|  |         } | ||||||
|  |         std::thread init_thread([]{ | ||||||
|  |             _initialize(); | ||||||
|  | 
 | ||||||
|  |             std::unique_lock lock{initialize_lock}; | ||||||
|  |             auto callbacks = std::move(initialize_callbacks); | ||||||
|  |             initialize_state = 1; | ||||||
|  |             lock.unlock(); | ||||||
|  | 
 | ||||||
|  |             for(auto& callback : callbacks) | ||||||
|  |                 callback(); | ||||||
|  |         }); | ||||||
|  |         threads::name(init_thread, tr("audio init")); | ||||||
|  |         init_thread.detach(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void await_initialized() { | ||||||
|  |         std::condition_variable cv{}; | ||||||
|  |         std::mutex m{}; | ||||||
|  | 
 | ||||||
|  |         std::unique_lock init_lock{initialize_lock}; | ||||||
|  |         if(initialize_state != 2) return; | ||||||
|  |         initialize_callbacks.emplace_back([&]{ cv.notify_all(); }); | ||||||
|  |         init_lock.unlock(); | ||||||
|  | 
 | ||||||
|  |         std::unique_lock m_lock{m}; | ||||||
|  |         cv.wait(m_lock); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool initialized() { | ||||||
|  |         std::unique_lock init_lock{initialize_lock}; | ||||||
|  |         return initialize_state == 1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void finalize() { | ||||||
|  |         await_initialized(); | ||||||
|  |         _finalize(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool AudioDevicePlayback::start(std::string &error) { | ||||||
|  |         std::lock_guard lock{this->state_lock}; | ||||||
|  |         if(this->running) return true; | ||||||
|  | 
 | ||||||
|  |         if(!this->impl_start(error)) { | ||||||
|  |             log_error(category::audio, tr("Failed to start playback: {}"), error); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         this->running = true; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDevicePlayback::stop_if_possible() { | ||||||
|  |         std::lock_guard lock{this->state_lock}; | ||||||
|  |         { | ||||||
|  |             std::lock_guard s_lock{this->source_lock}; | ||||||
|  |             if(!this->_sources.empty()) return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this->impl_stop(); | ||||||
|  |         this->running = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDevicePlayback::stop() { | ||||||
|  |         std::lock_guard lock{this->state_lock}; | ||||||
|  |         if(this->running) return; | ||||||
|  | 
 | ||||||
|  |         this->impl_stop(); | ||||||
|  |         this->running = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDevicePlayback::register_source(Source* source) { | ||||||
|  |         std::lock_guard s_lock{this->source_lock}; | ||||||
|  |         this->_sources.push_back(source); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDevicePlayback::remove_source(Source* source) { | ||||||
|  |         std::lock_guard s_lock{this->source_lock}; | ||||||
|  |         auto index = find(this->_sources.begin(), this->_sources.end(), source); | ||||||
|  |         if(index == this->_sources.end()) return; | ||||||
|  | 
 | ||||||
|  |         this->_sources.erase(index); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | #define TMP_BUFFER_SIZE 8096 | ||||||
|  |     void AudioDevicePlayback::fill_buffer(void *buffer, size_t samples, size_t channels) { | ||||||
|  |         std::lock_guard lock{this->source_lock}; | ||||||
|  | 
 | ||||||
|  |         const auto size = this->_sources.size(); | ||||||
|  |         if(size == 1) { | ||||||
|  |             this->_sources.front()->fill_buffer(buffer, samples, channels); | ||||||
|  |         } else if(size > 1) { | ||||||
|  |             this->_sources.front()->fill_buffer(buffer, samples, channels); | ||||||
|  |             uint8_t tmp_buffer[TMP_BUFFER_SIZE]; | ||||||
|  |             if(sizeof(float) * samples * channels > TMP_BUFFER_SIZE) { | ||||||
|  |                 log_warn(category::audio, tr("Dropping input source data because of too small merge buffer")); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for(auto it = this->_sources.begin() + 1; it != this->_sources.end(); it++) { | ||||||
|  |                 (*it)->fill_buffer(tmp_buffer, samples, channels); | ||||||
|  |                 merge::merge_sources(buffer, buffer, tmp_buffer, channels, samples); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             memset(buffer, 0, samples * channels * sizeof(float)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool AudioDeviceRecord::start(std::string &error) { | ||||||
|  |         std::lock_guard lock{this->state_lock}; | ||||||
|  |         if(this->running) return true; | ||||||
|  | 
 | ||||||
|  |         if(!this->impl_start(error)) { | ||||||
|  |             log_error(category::audio, tr("Failed to start record: {}"), error); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         this->running = true; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDeviceRecord::stop_if_possible() { | ||||||
|  |         std::lock_guard lock{this->state_lock}; | ||||||
|  |         { | ||||||
|  |             std::lock_guard s_lock{this->consumer_lock}; | ||||||
|  |             if(!this->_consumers.empty()) return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this->impl_stop(); | ||||||
|  |         this->running = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDeviceRecord::stop() { | ||||||
|  |         std::lock_guard lock{this->state_lock}; | ||||||
|  |         if(this->running) return; | ||||||
|  | 
 | ||||||
|  |         this->impl_stop(); | ||||||
|  |         this->running = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDeviceRecord::register_consumer(Consumer* source) { | ||||||
|  |         std::lock_guard s_lock{this->consumer_lock}; | ||||||
|  |         this->_consumers.push_back(source); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AudioDeviceRecord::remove_consumer(Consumer* source) { | ||||||
|  |         std::lock_guard s_lock{this->consumer_lock}; | ||||||
|  |         auto index = find(this->_consumers.begin(), this->_consumers.end(), source); | ||||||
|  |         if(index == this->_consumers.end()) return; | ||||||
|  | 
 | ||||||
|  |         this->_consumers.erase(index); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								native/serverconnection/src/audio/driver/AudioDriver.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								native/serverconnection/src/audio/driver/AudioDriver.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <mutex> | ||||||
|  | #include <deque> | ||||||
|  | #include <memory> | ||||||
|  | #include <iostream> | ||||||
|  | #include <functional> | ||||||
|  | 
 | ||||||
|  | namespace tc::audio { | ||||||
|  |     class AudioDeviceRecord { | ||||||
|  |         public: | ||||||
|  |             class Consumer { | ||||||
|  |                 public: | ||||||
|  |                     virtual void consume(const void* /* buffer */, size_t /* samples */, size_t /* channel count */) = 0; | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] bool start(std::string& /* error */); | ||||||
|  |             void stop_if_possible(); | ||||||
|  |             void stop(); | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline std::vector<Consumer*> consumer() { | ||||||
|  |                 std::lock_guard lock{this->consumer_lock}; | ||||||
|  |                 return this->_consumers; | ||||||
|  |             } | ||||||
|  |             void register_consumer(Consumer* /* source */); | ||||||
|  |             void remove_consumer(Consumer* /* source */); | ||||||
|  | 
 | ||||||
|  |         protected: | ||||||
|  |             virtual bool impl_start(std::string& /* error */) = 0; | ||||||
|  |             virtual void impl_stop() = 0; | ||||||
|  | 
 | ||||||
|  |             std::mutex state_lock{}; | ||||||
|  |             bool running{false}; | ||||||
|  | 
 | ||||||
|  |             std::mutex consumer_lock{}; | ||||||
|  |             std::vector<Consumer*> _consumers{}; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     class AudioDevicePlayback { | ||||||
|  |         public: | ||||||
|  |             class Source { | ||||||
|  |                 public: | ||||||
|  |                     virtual void fill_buffer(void* /* target */, size_t /* samples */, size_t /* channel count */) = 0; | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] bool start(std::string& /* error */); | ||||||
|  |             void stop_if_possible(); | ||||||
|  |             void stop(); | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline std::vector<Source*> sources() { | ||||||
|  |                 std::lock_guard lock{this->source_lock}; | ||||||
|  |                 return this->_sources; | ||||||
|  |             } | ||||||
|  |             void register_source(Source* /* source */); | ||||||
|  |             void remove_source(Source* /* source */); | ||||||
|  | 
 | ||||||
|  |         protected: | ||||||
|  |             virtual bool impl_start(std::string& /* error */) = 0; | ||||||
|  |             virtual void impl_stop() = 0; | ||||||
|  | 
 | ||||||
|  |             void fill_buffer(void* /* target */, size_t /* samples */, size_t /* channel count */); | ||||||
|  | 
 | ||||||
|  |             std::mutex state_lock{}; | ||||||
|  |             bool running{false}; | ||||||
|  | 
 | ||||||
|  |             std::mutex source_lock{}; | ||||||
|  |             std::vector<Source*> _sources{}; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     class AudioDevice { | ||||||
|  |         public: | ||||||
|  |             /* information */ | ||||||
|  |             [[nodiscard]] virtual std::string id() const = 0; | ||||||
|  |             [[nodiscard]] virtual std::string name() const = 0; | ||||||
|  |             [[nodiscard]] virtual std::string driver() const = 0; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] virtual bool is_input_supported() const = 0; | ||||||
|  |             [[nodiscard]] virtual bool is_output_supported() const = 0; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] virtual bool is_input_default() const = 0; | ||||||
|  |             [[nodiscard]] virtual bool is_output_default() const = 0; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] virtual std::shared_ptr<AudioDevicePlayback> playback() = 0; | ||||||
|  |             [[nodiscard]] virtual std::shared_ptr<AudioDeviceRecord> record() = 0; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     typedef std::function<void()> initialize_callback_t; | ||||||
|  | 
 | ||||||
|  |     extern void finalize(); | ||||||
|  |     extern void initialize(const initialize_callback_t& /* callback */ = []{}); | ||||||
|  |     extern void await_initialized(); | ||||||
|  |     extern bool initialized(); | ||||||
|  | 
 | ||||||
|  |     extern std::deque<std::shared_ptr<AudioDevice>> devices(); | ||||||
|  |     extern std::shared_ptr<AudioDevice> find_device_by_id(const std::string_view& /* id */, bool /* input */); | ||||||
|  | } | ||||||
							
								
								
									
										260
									
								
								native/serverconnection/src/audio/driver/SoundIO.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								native/serverconnection/src/audio/driver/SoundIO.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,260 @@ | |||||||
|  | //
 | ||||||
|  | // Created by wolverindev on 07.02.20.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include "SoundIO.h" | ||||||
|  | #include <algorithm> | ||||||
|  | #include "../../logger.h" | ||||||
|  | 
 | ||||||
|  | using namespace tc::audio; | ||||||
|  | 
 | ||||||
|  | std::mutex SoundIOBackendHandler::backend_lock{}; | ||||||
|  | std::vector<std::shared_ptr<SoundIOBackendHandler>> SoundIOBackendHandler::backends{}; | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<SoundIOBackendHandler> SoundIOBackendHandler::get_backend(SoundIoBackend backend_type) { | ||||||
|  |     std::lock_guard lock{backend_lock}; | ||||||
|  |     for(auto& backend : SoundIOBackendHandler::backends) | ||||||
|  |         if(backend->backend == backend_type) | ||||||
|  |             return backend; | ||||||
|  | 
 | ||||||
|  |     return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::initialize_all() { | ||||||
|  |     std::lock_guard lock{backend_lock}; | ||||||
|  | 
 | ||||||
|  |     for(const auto& backend : { | ||||||
|  |             SoundIoBackendJack, | ||||||
|  |             SoundIoBackendPulseAudio, | ||||||
|  |             SoundIoBackendAlsa, | ||||||
|  |             SoundIoBackendCoreAudio, | ||||||
|  |             SoundIoBackendWasapi, | ||||||
|  |             SoundIoBackendDummy | ||||||
|  |     }) { | ||||||
|  |         if(!soundio_have_backend(backend)) { | ||||||
|  |             log_debug(category::audio, tr("Skipping audio backend {} because its not supported on this platform."), soundio_backend_name(backend)); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         auto handler = std::make_shared<SoundIOBackendHandler>(backend); | ||||||
|  |         if(std::string error{}; !handler->initialize(error)) { | ||||||
|  |             log_error(category::audio, tr("Failed to initialize sound backed {}: {}"), soundio_backend_name(backend), error); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         backends.push_back(handler); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::stable_sort(backends.begin(), backends.end(), [](const auto& a, const auto& b) { return a->priority() > b->priority(); }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::connect_all() { | ||||||
|  |     std::string error{}; | ||||||
|  | 
 | ||||||
|  |     std::lock_guard lock{backend_lock}; | ||||||
|  |     for(const auto& backend : backends) | ||||||
|  |         if(!backend->connect(error)) | ||||||
|  |             log_error(category::audio, tr("Failed to connect to audio backend {}: {}"), backend->name(), error); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::shutdown_all() { | ||||||
|  |     std::lock_guard lock{backend_lock}; | ||||||
|  |     for(auto& entry : backends) | ||||||
|  |         entry->shutdown(); | ||||||
|  |     backends.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SoundIOBackendHandler::SoundIOBackendHandler(SoundIoBackend backed) : backend{backed} {} | ||||||
|  | SoundIOBackendHandler::~SoundIOBackendHandler() { | ||||||
|  |     this->shutdown(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIOBackendHandler::initialize(std::string &error) { | ||||||
|  |     assert(!this->soundio_handle); | ||||||
|  | 
 | ||||||
|  |     this->soundio_handle = soundio_create(); | ||||||
|  |     if(!this->soundio_handle) { | ||||||
|  |         error = "out of memory"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->soundio_handle->userdata = this; | ||||||
|  |     this->soundio_handle->on_devices_change = [](auto handle){ | ||||||
|  |         reinterpret_cast<SoundIOBackendHandler*>(handle->userdata)->handle_device_change(); | ||||||
|  |     }; | ||||||
|  |     this->soundio_handle->on_backend_disconnect = [](auto handle, auto err){ | ||||||
|  |         reinterpret_cast<SoundIOBackendHandler*>(handle->userdata)->handle_backend_disconnect(err); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::shutdown() { | ||||||
|  |     if(!this->soundio_handle) return; | ||||||
|  | 
 | ||||||
|  |     soundio_destroy(this->soundio_handle); | ||||||
|  |     this->soundio_handle = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIOBackendHandler::connect(std::string &error, bool enforce) { | ||||||
|  |     if(!this->soundio_handle) { | ||||||
|  |         error = "invalid handle"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(this->_connected && !enforce) { | ||||||
|  |         error = "already connected"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto err = soundio_connect_backend(this->soundio_handle, this->backend); | ||||||
|  |     if(err) { | ||||||
|  |         error = soundio_strerror(err); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->soundio_handle->app_name = "TeaClient"; | ||||||
|  |     this->_connected = true; | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         auto begin = std::chrono::system_clock::now(); | ||||||
|  |         soundio_flush_events(this->soundio_handle); | ||||||
|  |         auto end = std::chrono::system_clock::now(); | ||||||
|  |         log_debug(category::audio, tr("Flushed connect events within {}ms for backend {}"), | ||||||
|  |                 std::chrono::ceil<std::chrono::milliseconds>(end - begin).count(), | ||||||
|  |                 this->name()); | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::disconnect() { | ||||||
|  |     if(!this->soundio_handle || !this->_connected) return; | ||||||
|  | 
 | ||||||
|  |     soundio_disconnect(this->soundio_handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::handle_device_change() { | ||||||
|  |     log_debug(category::audio, tr("Device list changed for backend {}. Reindexing devices."), this->name()); | ||||||
|  | 
 | ||||||
|  |     std::lock_guard lock{this->device_lock}; | ||||||
|  |     this->_default_output_device.reset(); | ||||||
|  |     this->_default_input_device.reset(); | ||||||
|  |     this->cached_input_devices.clear(); | ||||||
|  |     this->cached_output_devices.clear(); | ||||||
|  | 
 | ||||||
|  |     if(!this->_connected || !this->soundio_handle) return; | ||||||
|  | 
 | ||||||
|  |     size_t input_devices{0}, output_devices{0}; | ||||||
|  |     auto default_input_device{soundio_default_input_device_index(this->soundio_handle)}; | ||||||
|  |     for(int i = 0; i < soundio_input_device_count(this->soundio_handle); i++) { | ||||||
|  |         auto dev = soundio_get_input_device(this->soundio_handle, i); | ||||||
|  |         if(!dev) { | ||||||
|  |             log_warn(category::audio, tr("Failed to get input device at index {} for backend {}."), i, this->name()); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         if(dev->probe_error) { | ||||||
|  |             log_trace(category::audio, tr("Skipping input device {} ({}) for backend {} because of probe error: {}"), dev->id, dev->name, this->name(), soundio_strerror(dev->probe_error)); | ||||||
|  |             soundio_device_unref(dev); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         auto device = std::make_shared<SoundIODevice>(dev, this->name(), i == default_input_device, true); | ||||||
|  |         log_trace(category::audio, tr("Found input device {} ({})."), dev->id, dev->name); | ||||||
|  |         this->cached_input_devices.push_back(device); | ||||||
|  |         if(i == default_input_device) | ||||||
|  |             this->_default_input_device = device; | ||||||
|  |         input_devices++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto default_output_device{soundio_default_output_device_index(this->soundio_handle)}; | ||||||
|  |     for(int i = 0; i < soundio_output_device_count(this->soundio_handle); i++) { | ||||||
|  |         auto dev = soundio_get_output_device(this->soundio_handle, i); | ||||||
|  |         if(!dev) { | ||||||
|  |             log_warn(category::audio, tr("Failed to get output device at index {} for backend {}."), i, this->name()); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         if(dev->probe_error) { | ||||||
|  |             log_trace(category::audio, tr("Skipping output device {} ({}) for backend {} because of probe error: {}"), dev->id, dev->name, this->name(), soundio_strerror(dev->probe_error)); | ||||||
|  |             soundio_device_unref(dev); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         auto device = std::make_shared<SoundIODevice>(dev, this->name(), i == default_output_device, true); | ||||||
|  |         log_trace(category::audio, tr("Found output device {} ({})."), dev->id, dev->name); | ||||||
|  |         this->cached_output_devices.push_back(device); | ||||||
|  |         if(i == default_output_device) | ||||||
|  |             this->_default_output_device = device; | ||||||
|  |         output_devices++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     log_info(category::audio, tr("Queried devices for backend {}, resulting in {} input and {} output devices."), | ||||||
|  |             this->name(), | ||||||
|  |             input_devices, | ||||||
|  |             output_devices | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOBackendHandler::handle_backend_disconnect(int error) { | ||||||
|  |     log_info(category::audio, tr("Backend {} disconnected with error {}."), this->name(), soundio_strerror(error)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SoundIODevice::SoundIODevice(struct ::SoundIoDevice *dev, std::string driver, bool default_, bool owned) : device_handle{dev}, driver_name{std::move(driver)}, _default{default_} { | ||||||
|  |     if(!owned) soundio_device_ref(dev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SoundIODevice::~SoundIODevice() { | ||||||
|  |     soundio_device_unref(this->device_handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string SoundIODevice::id() const { | ||||||
|  |     return this->device_handle->id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string SoundIODevice::name() const { | ||||||
|  |     return this->device_handle->name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string SoundIODevice::driver() const { | ||||||
|  |     return this->driver_name; /* we do not use this->device_handle->soundio->current_backend because the soundio could be null */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIODevice::is_input_supported() const { | ||||||
|  |     return this->device_handle->aim == SoundIoDeviceAimInput; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIODevice::is_output_supported() const { | ||||||
|  |     return this->device_handle->aim == SoundIoDeviceAimOutput; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIODevice::is_input_default() const { | ||||||
|  |     return this->_default && this->is_input_supported(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIODevice::is_output_default() const { | ||||||
|  |     return this->_default && this->is_output_supported(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<AudioDevicePlayback> SoundIODevice::playback() { | ||||||
|  |     if(!this->is_output_supported()) { | ||||||
|  |         log_warn(category::audio, tr("Tried to create playback manager for device which does not supports it.")); | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::lock_guard lock{this->io_lock}; | ||||||
|  |     if(!this->_playback) | ||||||
|  |         this->_playback = std::make_shared<SoundIOPlayback>(this->device_handle); | ||||||
|  |     return this->_playback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<AudioDeviceRecord> SoundIODevice::record() { | ||||||
|  |     if(!this->is_input_supported()) { | ||||||
|  |         log_warn(category::audio, tr("Tried to create record manager for device which does not supports it.")); | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::lock_guard lock{this->io_lock}; | ||||||
|  |     if(!this->_record) | ||||||
|  |         this->_record = std::make_shared<SoundIORecord>(this->device_handle); | ||||||
|  |     return this->_record; | ||||||
|  | } | ||||||
							
								
								
									
										158
									
								
								native/serverconnection/src/audio/driver/SoundIO.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								native/serverconnection/src/audio/driver/SoundIO.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <soundio/soundio.h> | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  | #include <mutex> | ||||||
|  | #include "./AudioDriver.h" | ||||||
|  | 
 | ||||||
|  | namespace tc::audio { | ||||||
|  |     struct BackendPriority { | ||||||
|  |         static constexpr std::array<int, SoundIoBackendDummy + 1> mapping{ | ||||||
|  |                 /* SoundIoBackendNone */ -100, | ||||||
|  |                 /* SoundIoBackendJack */ 100, | ||||||
|  |                 /* SoundIoBackendPulseAudio */ 90, | ||||||
|  |                 /* SoundIoBackendAlsa */ 50, | ||||||
|  |                 /* SoundIoBackendCoreAudio */ 100, | ||||||
|  |                 /* SoundIoBackendWasapi */ 100, | ||||||
|  |                 /* SoundIoBackendDummy */ 0 | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         [[nodiscard]] static constexpr auto priority(SoundIoBackend backend) { | ||||||
|  |             if(backend >= mapping.size()) | ||||||
|  |                 return 0; | ||||||
|  |             return mapping[backend]; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     class SoundIOPlayback : public AudioDevicePlayback { | ||||||
|  |         public: | ||||||
|  |             constexpr static auto kChunkSize{960}; | ||||||
|  | 
 | ||||||
|  |             explicit SoundIOPlayback(struct ::SoundIoDevice* /* handle */); | ||||||
|  |             virtual ~SoundIOPlayback(); | ||||||
|  | 
 | ||||||
|  |         protected: | ||||||
|  |             bool impl_start(std::string& /* error */) override; | ||||||
|  |             void impl_stop() override; | ||||||
|  | 
 | ||||||
|  |         private: | ||||||
|  |             bool stream_invalid{false}; | ||||||
|  |             struct ::SoundIoDevice* device_handle{nullptr}; | ||||||
|  |             struct ::SoundIoOutStream* stream{nullptr}; | ||||||
|  | 
 | ||||||
|  |             struct ::SoundIoRingBuffer* buffer{nullptr}; | ||||||
|  | 
 | ||||||
|  |             void write_callback(int frame_count_min, int frame_count_max); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     class SoundIORecord : public AudioDeviceRecord { | ||||||
|  |         public: | ||||||
|  |             constexpr static auto kChunkSize{960}; | ||||||
|  | 
 | ||||||
|  |             explicit SoundIORecord(struct ::SoundIoDevice* /* handle */); | ||||||
|  |             virtual ~SoundIORecord(); | ||||||
|  | 
 | ||||||
|  |         protected: | ||||||
|  |             bool impl_start(std::string& /* error */) override; | ||||||
|  |             void impl_stop() override; | ||||||
|  | 
 | ||||||
|  |         private: | ||||||
|  |             bool stream_invalid{false}; | ||||||
|  |             struct ::SoundIoDevice* device_handle{nullptr}; | ||||||
|  |             struct ::SoundIoInStream* stream{nullptr}; | ||||||
|  | 
 | ||||||
|  |             struct ::SoundIoRingBuffer* buffer{nullptr}; | ||||||
|  | 
 | ||||||
|  |             void read_callback(int frame_count_min, int frame_count_max); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     class SoundIODevice : public AudioDevice { | ||||||
|  |         public: | ||||||
|  |             explicit SoundIODevice(struct ::SoundIoDevice* /* handle */, std::string /* driver */, bool /* default */, bool /* owned */); | ||||||
|  |             virtual ~SoundIODevice(); | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] std::string id() const override; | ||||||
|  |             [[nodiscard]] std::string name() const override; | ||||||
|  |             [[nodiscard]] std::string driver() const override; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] bool is_input_supported() const override; | ||||||
|  |             [[nodiscard]] bool is_output_supported() const override; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] bool is_input_default() const override; | ||||||
|  |             [[nodiscard]] bool is_output_default() const override; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] std::shared_ptr<AudioDevicePlayback> playback() override; | ||||||
|  |             [[nodiscard]] std::shared_ptr<AudioDeviceRecord> record() override; | ||||||
|  |         private: | ||||||
|  |             std::string driver_name{}; | ||||||
|  |             struct ::SoundIoDevice* device_handle{nullptr}; | ||||||
|  |             bool _default{false}; | ||||||
|  | 
 | ||||||
|  |             std::mutex io_lock{}; | ||||||
|  |             std::shared_ptr<SoundIOPlayback> _playback; | ||||||
|  |             std::shared_ptr<SoundIORecord> _record; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     class SoundIOBackendHandler { | ||||||
|  |         public: | ||||||
|  |             /* its sorted by priority */ | ||||||
|  |             static std::vector<std::shared_ptr<SoundIOBackendHandler>> all_backends() { | ||||||
|  |                 std::lock_guard lock{backend_lock}; | ||||||
|  |                 return backends;; | ||||||
|  |             } | ||||||
|  |             static std::shared_ptr<SoundIOBackendHandler> get_backend(SoundIoBackend backend); | ||||||
|  | 
 | ||||||
|  |             static void initialize_all(); | ||||||
|  |             static void connect_all(); | ||||||
|  |             static void shutdown_all(); | ||||||
|  | 
 | ||||||
|  |             explicit SoundIOBackendHandler(SoundIoBackend backed); | ||||||
|  |             virtual ~SoundIOBackendHandler(); | ||||||
|  | 
 | ||||||
|  |             bool initialize(std::string& error); | ||||||
|  |             void shutdown(); | ||||||
|  | 
 | ||||||
|  |             bool connect(std::string& /* error */, bool /* enforce */ = false); | ||||||
|  |             [[nodiscard]] inline bool connected() const { return this->_connected; } | ||||||
|  |             void disconnect(); | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline int priority() const { return BackendPriority::priority(this->backend); } | ||||||
|  |             [[nodiscard]] inline const char* name() const { return soundio_backend_name(this->backend); } | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline std::vector<std::shared_ptr<SoundIODevice>> input_devices() const { | ||||||
|  |                 std::lock_guard lock{this->device_lock}; | ||||||
|  |                 return this->cached_input_devices; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline std::vector<std::shared_ptr<SoundIODevice>> output_devices() const { | ||||||
|  |                 std::lock_guard lock{this->device_lock}; | ||||||
|  |                 return this->cached_output_devices; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline std::shared_ptr<SoundIODevice> default_input_device() const { | ||||||
|  |                 return this->_default_input_device; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] inline std::shared_ptr<SoundIODevice> default_output_device() const { | ||||||
|  |                 return this->_default_output_device; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const SoundIoBackend backend; | ||||||
|  |         private: | ||||||
|  |             static std::mutex backend_lock; | ||||||
|  |             static std::vector<std::shared_ptr<SoundIOBackendHandler>> backends; | ||||||
|  | 
 | ||||||
|  |             void handle_backend_disconnect(int /* error */); | ||||||
|  |             void handle_device_change(); | ||||||
|  | 
 | ||||||
|  |             bool _connected{false}; | ||||||
|  |             struct SoundIo* soundio_handle{nullptr}; | ||||||
|  | 
 | ||||||
|  |             mutable std::mutex device_lock{}; | ||||||
|  |             std::vector<std::shared_ptr<SoundIODevice>> cached_input_devices{}; | ||||||
|  |             std::vector<std::shared_ptr<SoundIODevice>> cached_output_devices{}; | ||||||
|  |             std::shared_ptr<SoundIODevice> _default_output_device{nullptr}; | ||||||
|  |             std::shared_ptr<SoundIODevice> _default_input_device{nullptr}; | ||||||
|  |     }; | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								native/serverconnection/src/audio/driver/SoundIOPlayback.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								native/serverconnection/src/audio/driver/SoundIOPlayback.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,145 @@ | |||||||
|  | //
 | ||||||
|  | // Created by wolverindev on 07.02.20.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include "SoundIO.h" | ||||||
|  | #include <algorithm> | ||||||
|  | #include "../../logger.h" | ||||||
|  | 
 | ||||||
|  | using namespace tc::audio; | ||||||
|  | 
 | ||||||
|  | SoundIOPlayback::SoundIOPlayback(struct ::SoundIoDevice *device) : device_handle{device} { | ||||||
|  |     soundio_device_ref(device); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SoundIOPlayback::~SoundIOPlayback() { | ||||||
|  |     soundio_device_unref(this->device_handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIOPlayback::impl_start(std::string &error) { | ||||||
|  |     assert(this->device_handle); | ||||||
|  | 
 | ||||||
|  |     this->buffer = soundio_ring_buffer_create(nullptr, kChunkSize * sizeof(float) * 2); /* 2 channels */ | ||||||
|  |     if(!buffer) { | ||||||
|  |         error = "failed to allocate the buffer"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     soundio_ring_buffer_clear(this->buffer); | ||||||
|  | 
 | ||||||
|  |     this->stream = soundio_outstream_create(this->device_handle); | ||||||
|  |     if(!this->stream) { | ||||||
|  |         error = "out of memory"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->stream->userdata = this; | ||||||
|  |     this->stream->format = SoundIoFormatFloat32LE; | ||||||
|  |     this->stream->software_latency = 0.02; | ||||||
|  | 
 | ||||||
|  |     this->stream->underflow_callback = [](auto str) { | ||||||
|  |         auto handle = reinterpret_cast<SoundIOPlayback*>(str->userdata); | ||||||
|  |         log_info(category::audio, tr("Having an underflow on {}"), handle->device_handle->id); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     this->stream->error_callback = [](auto str, int err) { | ||||||
|  |         auto handle = reinterpret_cast<SoundIOPlayback*>(str->userdata); | ||||||
|  |         log_info(category::audio, tr("Having an error on {}: {}. Aborting playback."), handle->device_handle->id, soundio_strerror(err)); | ||||||
|  |         handle->stream_invalid = true; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     this->stream->write_callback = [](struct SoundIoOutStream *str, int frame_count_min, int frame_count_max) { | ||||||
|  |         auto handle = reinterpret_cast<SoundIOPlayback*>(str->userdata); | ||||||
|  |         handle->write_callback(frame_count_min, frame_count_max); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     if(auto err = soundio_outstream_open(this->stream); err) { | ||||||
|  |         error = soundio_strerror(err) + std::string{" (open)"}; | ||||||
|  |         soundio_outstream_destroy(this->stream); | ||||||
|  |         this->stream = nullptr; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(false && this->stream->layout_error) { | ||||||
|  |         error = std::string{"failed to set audio layout: "} + soundio_strerror(this->stream->layout_error); | ||||||
|  |         soundio_outstream_destroy(this->stream); | ||||||
|  |         this->stream = nullptr; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(auto err = soundio_outstream_start(this->stream); err) { | ||||||
|  |         error = soundio_strerror(err) + std::string{" (start)"}; | ||||||
|  |         soundio_outstream_destroy(this->stream); | ||||||
|  |         this->stream = nullptr; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //TODO: Test for interleaved channel layout!
 | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOPlayback::impl_stop() { | ||||||
|  |     if(!this->stream) return; | ||||||
|  | 
 | ||||||
|  |     soundio_outstream_destroy(this->stream); | ||||||
|  |     this->stream = nullptr; | ||||||
|  | 
 | ||||||
|  |     soundio_ring_buffer_destroy(this->buffer); | ||||||
|  |     this->buffer = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIOPlayback::write_callback(int frame_count_min, int frame_count_max) { | ||||||
|  |     const struct SoundIoChannelLayout *layout = &this->stream->layout; | ||||||
|  | 
 | ||||||
|  |     struct SoundIoChannelArea *areas; | ||||||
|  |     int frames_left{frame_count_min}, err; | ||||||
|  | 
 | ||||||
|  |     if(frames_left < 120) | ||||||
|  |         frames_left = 120; | ||||||
|  |     if(frames_left > frame_count_max) | ||||||
|  |         frames_left = frame_count_max; | ||||||
|  | 
 | ||||||
|  |     while(frames_left > 0) { | ||||||
|  |         int frame_count{frames_left}; | ||||||
|  |         auto buffered = soundio_ring_buffer_fill_count(this->buffer) / (sizeof(float) * layout->channel_count); | ||||||
|  |         if(frame_count > buffered) { | ||||||
|  |             if(buffered == 0) { | ||||||
|  |                 const auto length = sizeof(float) * frame_count * layout->channel_count; | ||||||
|  |                 this->fill_buffer(soundio_ring_buffer_write_ptr(this->buffer), frame_count, layout->channel_count); | ||||||
|  |                 soundio_ring_buffer_advance_write_ptr(this->buffer, length); | ||||||
|  |             } else | ||||||
|  |                 frame_count = buffered; | ||||||
|  |         } | ||||||
|  |         if((err = soundio_outstream_begin_write(this->stream, &areas, &frame_count))) { | ||||||
|  |             log_warn(category::audio, tr("Failed to begin a write to the soundio buffer: {}"), err); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* test for interleaved */ | ||||||
|  |         { | ||||||
|  |             char* begin = areas[0].ptr - sizeof(float); | ||||||
|  |             for(size_t channel{0}; channel < layout->channel_count; channel++) { | ||||||
|  |                 if((begin += sizeof(float)) != areas[channel].ptr) { | ||||||
|  |                     log_error(category::audio, tr("Expected interleaved buffer, which it isn't")); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if(areas[channel].step != sizeof(float) * layout->channel_count) { | ||||||
|  |                     log_error(category::audio, tr("Invalid step size for channel {}"), channel); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const auto length = sizeof(float) * frame_count * layout->channel_count; | ||||||
|  |         memcpy(areas[0].ptr, soundio_ring_buffer_read_ptr(this->buffer), length); | ||||||
|  |         soundio_ring_buffer_advance_read_ptr(this->buffer, length); | ||||||
|  | 
 | ||||||
|  |         if((err = soundio_outstream_end_write(this->stream))) { | ||||||
|  |             log_warn(category::audio, tr("Failed to end a write to the soundio buffer: {}"), err); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         frames_left -= frame_count; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								native/serverconnection/src/audio/driver/SoundIORecord.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								native/serverconnection/src/audio/driver/SoundIORecord.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,145 @@ | |||||||
|  | //
 | ||||||
|  | // Created by wolverindev on 07.02.20.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include "SoundIO.h" | ||||||
|  | #include <algorithm> | ||||||
|  | #include "../../logger.h" | ||||||
|  | 
 | ||||||
|  | using namespace tc::audio; | ||||||
|  | 
 | ||||||
|  | SoundIORecord::SoundIORecord(struct ::SoundIoDevice *device) : device_handle{device} { | ||||||
|  |     soundio_device_ref(device); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SoundIORecord::~SoundIORecord() { | ||||||
|  |     soundio_device_unref(this->device_handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SoundIORecord::impl_start(std::string &error) { | ||||||
|  |     assert(this->device_handle); | ||||||
|  | 
 | ||||||
|  |     this->buffer = soundio_ring_buffer_create(nullptr, kChunkSize * sizeof(float) * 2); //2 Channels
 | ||||||
|  |     if(!buffer) { | ||||||
|  |         error = "failed to allocate the buffer"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     soundio_ring_buffer_clear(this->buffer); | ||||||
|  | 
 | ||||||
|  |     this->stream = soundio_instream_create(this->device_handle); | ||||||
|  |     if(!this->stream) { | ||||||
|  |         error = "out of memory"; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->stream->userdata = this; | ||||||
|  |     this->stream->format = SoundIoFormatFloat32LE; | ||||||
|  |     this->stream->software_latency = 0.02; | ||||||
|  | 
 | ||||||
|  |     this->stream->overflow_callback = [](auto str) { | ||||||
|  |         auto handle = reinterpret_cast<SoundIORecord*>(str->userdata); | ||||||
|  |         log_info(category::audio, tr("Having an overflow on {}"), handle->device_handle->id); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     this->stream->error_callback = [](auto str, int err) { | ||||||
|  |         auto handle = reinterpret_cast<SoundIORecord*>(str->userdata); | ||||||
|  |         log_info(category::audio, tr("Having an error on {}: {}. Aborting recording."), handle->device_handle->id, soundio_strerror(err)); | ||||||
|  |         handle->stream_invalid = true; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     this->stream->read_callback = [](struct SoundIoInStream *str, int frame_count_min, int frame_count_max) { | ||||||
|  |         auto handle = reinterpret_cast<SoundIORecord*>(str->userdata); | ||||||
|  |         handle->read_callback(frame_count_min, frame_count_max); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     if(auto err = soundio_instream_open(this->stream); err) { | ||||||
|  |         error = soundio_strerror(err) + std::string{" (open)"}; | ||||||
|  |         soundio_instream_destroy(this->stream); | ||||||
|  |         this->stream = nullptr; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(false && this->stream->layout_error) { | ||||||
|  |         error = std::string{"failed to set audio layout: "} + soundio_strerror(this->stream->layout_error); | ||||||
|  |         soundio_instream_destroy(this->stream); | ||||||
|  |         this->stream = nullptr; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(auto err = soundio_instream_start(this->stream); err) { | ||||||
|  |         error = soundio_strerror(err) + std::string{" (start)"}; | ||||||
|  |         soundio_instream_destroy(this->stream); | ||||||
|  |         this->stream = nullptr; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //TODO: Test for interleaved channel layout!
 | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIORecord::impl_stop() { | ||||||
|  |     if(!this->stream) return; | ||||||
|  | 
 | ||||||
|  |     soundio_instream_destroy(this->stream); | ||||||
|  |     this->stream = nullptr; | ||||||
|  | 
 | ||||||
|  |     soundio_ring_buffer_destroy(this->buffer); | ||||||
|  |     this->buffer = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SoundIORecord::read_callback(int frame_count_min, int frame_count_max) { | ||||||
|  |     const struct SoundIoChannelLayout *layout = &this->stream->layout; | ||||||
|  | 
 | ||||||
|  |     struct SoundIoChannelArea *areas; | ||||||
|  | 
 | ||||||
|  |     int frames_left{frame_count_max}; | ||||||
|  |     int buffer_samples = soundio_ring_buffer_free_count(this->buffer) / (sizeof(float) * layout->channel_count); | ||||||
|  | 
 | ||||||
|  |     while(frames_left > 0) { | ||||||
|  |         int frame_count{frames_left}; | ||||||
|  |         if(frame_count > buffer_samples) | ||||||
|  |             frame_count = buffer_samples; | ||||||
|  |         if(auto err = soundio_instream_begin_read(this->stream, &areas, &frame_count); err) { | ||||||
|  |             log_error(category::audio, tr("Failed to begin read from input stream buffer: {}"), soundio_strerror(err)); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* test for interleaved */ | ||||||
|  |         { | ||||||
|  |             char* begin = areas[0].ptr - sizeof(float); | ||||||
|  |             for(size_t channel{0}; channel < layout->channel_count; channel++) { | ||||||
|  |                 if((begin += sizeof(float)) != areas[channel].ptr) { | ||||||
|  |                     log_error(category::audio, tr("Expected interleaved buffer, which it isn't")); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if(areas[channel].step != sizeof(float) * layout->channel_count) { | ||||||
|  |                     log_error(category::audio, tr("Invalid step size for channel {}"), channel); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const auto length = sizeof(float) * layout->channel_count * frame_count; | ||||||
|  |         memcpy(soundio_ring_buffer_write_ptr(this->buffer), areas[0].ptr, length); | ||||||
|  |         soundio_ring_buffer_advance_write_ptr(this->buffer, length); | ||||||
|  |         buffer_samples -= frame_count; | ||||||
|  |         frames_left -= frame_count; | ||||||
|  | 
 | ||||||
|  |         if(buffer_samples == 0) { | ||||||
|  |             std::lock_guard consumer{this->consumer_lock}; | ||||||
|  |             const auto byte_count = soundio_ring_buffer_fill_count(this->buffer); | ||||||
|  |             const auto frame_count = byte_count / (sizeof(float) * layout->channel_count); | ||||||
|  |             for(auto& consumer : this->_consumers) | ||||||
|  |                 consumer->consume(soundio_ring_buffer_read_ptr(this->buffer), frame_count, layout->channel_count); | ||||||
|  |             soundio_ring_buffer_advance_read_ptr(this->buffer, byte_count); | ||||||
|  |             buffer_samples = frame_count; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(auto err = soundio_instream_end_read(this->stream); err) { | ||||||
|  |             log_error(category::audio, tr("Failed to close input stream buffer: {}"), soundio_strerror(err)); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -121,25 +121,10 @@ NAN_METHOD(AudioOutputStreamWrapper::_delete) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr<AudioOutputSource>& handle, void *source, size_t samples, bool interleaved) { | ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr<AudioOutputSource>& handle, void *source, size_t samples, bool interleaved) { | ||||||
| 	ssize_t result = 0; | 	if(interleaved) | ||||||
| 	if(interleaved) { | 		return handle->enqueue_samples(source, samples); | ||||||
| 		result = handle->enqueue_samples(source, samples); |     else | ||||||
| 	} else { | 	    return handle->enqueue_samples_no_interleave(source, samples); | ||||||
| 		auto buffer = SampleBuffer::allocate(handle->channel_count, samples); |  | ||||||
| 		auto src_buffer = (float*) source; |  | ||||||
| 		auto target_buffer = (float*) buffer->sample_data; |  | ||||||
| 
 |  | ||||||
| 		while (samples-- > 0) { |  | ||||||
| 			*target_buffer = *src_buffer; |  | ||||||
| 			*(target_buffer + 1) = *(src_buffer + buffer->sample_size); |  | ||||||
| 
 |  | ||||||
| 			target_buffer += 2; |  | ||||||
| 			src_buffer++; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		result = handle->enqueue_samples(buffer); |  | ||||||
| 	} |  | ||||||
| 	return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(AudioOutputStreamWrapper::_write_data) { | NAN_METHOD(AudioOutputStreamWrapper::_write_data) { | ||||||
| @ -198,6 +183,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_write_data_rated) { | |||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		//TODO: Use a tmp preallocated buffer here!
 | ||||||
| 		ssize_t target_samples = client->_resampler->estimated_output_size(samples); | 		ssize_t target_samples = client->_resampler->estimated_output_size(samples); | ||||||
| 		auto buffer = SampleBuffer::allocate(handle->channel_count, max((size_t) samples, (size_t) target_samples)); | 		auto buffer = SampleBuffer::allocate(handle->channel_count, max((size_t) samples, (size_t) target_samples)); | ||||||
| 		auto source_buffer = js_buffer.Data(); | 		auto source_buffer = js_buffer.Data(); | ||||||
| @ -224,7 +210,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_write_data_rated) { | |||||||
| 
 | 
 | ||||||
| 		buffer->sample_index = 0; | 		buffer->sample_index = 0; | ||||||
| 		buffer->sample_size = target_samples; | 		buffer->sample_size = target_samples; | ||||||
| 		info.GetReturnValue().Set((int32_t) handle->enqueue_samples(buffer)); | 		info.GetReturnValue().Set((int32_t) handle->enqueue_samples(buffer->sample_data, target_samples)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -237,7 +223,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_latency) { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	info.GetReturnValue().Set((float) handle->min_buffer / (float) handle->sample_rate); | 	info.GetReturnValue().Set((float) handle->min_buffered_samples / (float) handle->sample_rate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) { | NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) { | ||||||
| @ -254,7 +240,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	handle->min_buffer = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); | 	handle->min_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) { | NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) { | ||||||
| @ -266,7 +252,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	info.GetReturnValue().Set((float) handle->max_latency / (float) handle->sample_rate); | 	info.GetReturnValue().Set((float) handle->max_latency() / (float) handle->sample_rate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) { | NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) { | ||||||
| @ -283,7 +269,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	handle->max_latency = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); | 	handle->max_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(AudioOutputStreamWrapper::_flush_buffer) { | NAN_METHOD(AudioOutputStreamWrapper::_flush_buffer) { | ||||||
|  | |||||||
| @ -1,10 +1,9 @@ | |||||||
| #include <misc/base64.h> |  | ||||||
| #include <misc/digest.h> | #include <misc/digest.h> | ||||||
| #include <include/NanStrings.h> | #include <include/NanStrings.h> | ||||||
| #include "AudioPlayer.h" | #include "AudioPlayer.h" | ||||||
| #include "../AudioOutput.h" | #include "../AudioOutput.h" | ||||||
| #include "../AudioDevice.h" |  | ||||||
| #include "AudioOutputStream.h" | #include "AudioOutputStream.h" | ||||||
|  | #include "../../logger.h" | ||||||
| 
 | 
 | ||||||
| using namespace tc; | using namespace tc; | ||||||
| using namespace tc::audio; | using namespace tc::audio; | ||||||
| @ -22,6 +21,11 @@ NAN_MODULE_INIT(player::init_js) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(audio::available_devices) { | NAN_METHOD(audio::available_devices) { | ||||||
|  |     if(!audio::initialized()) { | ||||||
|  |         Nan::ThrowError(tr("audio hasn't been initialized!")); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 	auto devices = audio::devices(); | 	auto devices = audio::devices(); | ||||||
| 	auto result = Nan::New<v8::Array>(devices.size()); | 	auto result = Nan::New<v8::Array>(devices.size()); | ||||||
| 
 | 
 | ||||||
| @ -29,17 +33,15 @@ NAN_METHOD(audio::available_devices) { | |||||||
| 		auto device_info = Nan::New<v8::Object>(); | 		auto device_info = Nan::New<v8::Object>(); | ||||||
| 		auto device = devices[index]; | 		auto device = devices[index]; | ||||||
| 
 | 
 | ||||||
| 		Nan::Set(device_info, Nan::LocalString("name"), Nan::LocalStringUTF8(device->name)); | 		Nan::Set(device_info, Nan::LocalString("name"), Nan::LocalStringUTF8(device->name())); | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("driver").ToLocalChecked(), Nan::New<v8::String>(device->driver).ToLocalChecked()); | 		Nan::Set(device_info, Nan::New<v8::String>("driver").ToLocalChecked(), Nan::New<v8::String>(device->driver()).ToLocalChecked()); | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("device_id").ToLocalChecked(), Nan::New<v8::String>(base64::encode(digest::sha1(device->name + device->driver))).ToLocalChecked()); | 		Nan::Set(device_info, Nan::New<v8::String>("device_id").ToLocalChecked(), Nan::New<v8::String>(device->id()).ToLocalChecked()); | ||||||
| 
 | 
 | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("input_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->input_supported)); | 		Nan::Set(device_info, Nan::New<v8::String>("input_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_input_supported())); | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("output_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->output_supported)); | 		Nan::Set(device_info, Nan::New<v8::String>("output_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_output_supported())); | ||||||
| 
 | 
 | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("input_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_default_input)); | 		Nan::Set(device_info, Nan::New<v8::String>("input_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_input_default())); | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("output_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_default_output)); | 		Nan::Set(device_info, Nan::New<v8::String>("output_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_output_default())); | ||||||
| 
 |  | ||||||
| 		Nan::Set(device_info, Nan::New<v8::String>("device_index").ToLocalChecked(), Nan::New<v8::Number>(device->device_id)); |  | ||||||
| 
 | 
 | ||||||
| 		Nan::Set(result, index, device_info); | 		Nan::Set(result, index, device_info); | ||||||
| 	} | 	} | ||||||
| @ -47,13 +49,48 @@ NAN_METHOD(audio::available_devices) { | |||||||
| 	info.GetReturnValue().Set(result); | 	info.GetReturnValue().Set(result); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(player::current_playback_device) { | NAN_METHOD(audio::await_initialized_js) { | ||||||
| 	if(!global_audio_output) { |     if(info.Length() != 1 || !info[0]->IsFunction()) { | ||||||
| 		info.GetReturnValue().Set(paNoDevice); |         Nan::ThrowError(tr("Invalid arguments")); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 	info.GetReturnValue().Set(global_audio_output->current_device()); |     if(audio::initialized()) { | ||||||
|  |         (void) info[0].As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); | ||||||
|  |     } else { | ||||||
|  |         auto _callback = std::make_unique<Nan::Persistent<v8::Function>>(info[0].As<v8::Function>()); | ||||||
|  | 
 | ||||||
|  |         auto _async_callback = Nan::async_callback([call = std::move(_callback)] { | ||||||
|  |             Nan::HandleScope scope; | ||||||
|  |             auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate()); | ||||||
|  | 
 | ||||||
|  |             (void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); | ||||||
|  |             call->Reset(); | ||||||
|  |         }).option_destroyed_execute(true); | ||||||
|  | 
 | ||||||
|  |         audio::initialize([_async_callback] { | ||||||
|  |             _async_callback.call(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | NAN_METHOD(audio::initialized_js) { | ||||||
|  |     info.GetReturnValue().Set(audio::initialized()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | NAN_METHOD(player::current_playback_device) { | ||||||
|  | 	if(!global_audio_output) { | ||||||
|  | 		info.GetReturnValue().Set(Nan::Undefined()); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto device = global_audio_output->current_device(); | ||||||
|  | 	if(!device) { | ||||||
|  |         info.GetReturnValue().Set(Nan::Undefined()); | ||||||
|  |         return; | ||||||
|  | 	} | ||||||
|  | 	info.GetReturnValue().Set(Nan::LocalString(device->id())); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(player::set_playback_device) { | NAN_METHOD(player::set_playback_device) { | ||||||
| @ -62,24 +99,33 @@ NAN_METHOD(player::set_playback_device) { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(info.Length() != 1 || !info[0]->IsNumber()) { | 	const auto null_device = info[0]->IsNullOrUndefined(); | ||||||
|  | 	if(info.Length() != 1 || !(info[0]->IsString() || null_device)) { | ||||||
| 		Nan::ThrowError("invalid arguments"); | 		Nan::ThrowError("invalid arguments"); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	std::string error; | 	auto device = null_device ? nullptr : audio::find_device_by_id(*Nan::Utf8String(info[0]), false); | ||||||
| 	if(!global_audio_output->open_device(error, info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0))) { |     if(!device && !null_device) { | ||||||
| 		Nan::ThrowError(Nan::New<v8::String>("failed to open device (" + error + ")").ToLocalChecked()); |         Nan::ThrowError("invalid device id"); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 	if(!global_audio_output->playback()) { | 	std::string error; | ||||||
| 		Nan::ThrowError("failed to open playback stream"); | 
 | ||||||
|  | 	global_audio_output->set_device(device); | ||||||
|  | 	if(!global_audio_output->playback(error)) { | ||||||
|  | 		Nan::ThrowError(error.c_str()); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(player::create_stream) { | NAN_METHOD(player::create_stream) { | ||||||
|  |     if(!audio::initialized()) { | ||||||
|  |         Nan::ThrowError(tr("audio hasn't been initialized yet")); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 	if(!global_audio_output) { | 	if(!global_audio_output) { | ||||||
| 		Nan::ThrowError("Global audio output hasn't been yet initialized!"); | 		Nan::ThrowError("Global audio output hasn't been yet initialized!"); | ||||||
| 		return; | 		return; | ||||||
|  | |||||||
| @ -2,9 +2,10 @@ | |||||||
| 
 | 
 | ||||||
| #include <nan.h> | #include <nan.h> | ||||||
| 
 | 
 | ||||||
| namespace tc { | namespace tc::audio { | ||||||
| 	namespace audio { |  | ||||||
|     extern NAN_METHOD(available_devices); |     extern NAN_METHOD(available_devices); | ||||||
|  |     extern NAN_METHOD(await_initialized_js); | ||||||
|  |     extern NAN_METHOD(initialized_js); | ||||||
| 
 | 
 | ||||||
|     namespace player { |     namespace player { | ||||||
|         extern NAN_MODULE_INIT(init_js); |         extern NAN_MODULE_INIT(init_js); | ||||||
| @ -30,4 +31,3 @@ namespace tc { | |||||||
|      */ |      */ | ||||||
|     } |     } | ||||||
| } | } | ||||||
| } |  | ||||||
| @ -1,4 +1,5 @@ | |||||||
| #include <utility> | #include <utility> | ||||||
|  | #include <NanStrings.h> | ||||||
| 
 | 
 | ||||||
| #include "AudioRecorder.h" | #include "AudioRecorder.h" | ||||||
| #include "AudioConsumer.h" | #include "AudioConsumer.h" | ||||||
| @ -14,6 +15,10 @@ NAN_MODULE_INIT(recorder::init_js) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(recorder::create_recorder) { | NAN_METHOD(recorder::create_recorder) { | ||||||
|  |     if(!audio::initialized()) { | ||||||
|  |         Nan::ThrowError(tr("audio hasn't been initialized yet")); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 	auto input = make_shared<AudioInput>(2, 48000); | 	auto input = make_shared<AudioInput>(2, 48000); | ||||||
| 	auto wrapper = new AudioRecorderWrapper(input); | 	auto wrapper = new AudioRecorderWrapper(input); | ||||||
| 	auto js_object = Nan::NewInstance(Nan::New(AudioRecorderWrapper::constructor())).ToLocalChecked(); | 	auto js_object = Nan::NewInstance(Nan::New(AudioRecorderWrapper::constructor())).ToLocalChecked(); | ||||||
| @ -122,42 +127,50 @@ NAN_METHOD(AudioRecorderWrapper::_get_device) { | |||||||
| 	auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder()); | 	auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder()); | ||||||
| 	auto input = handle->_input; | 	auto input = handle->_input; | ||||||
| 
 | 
 | ||||||
| 	info.GetReturnValue().Set(input->current_device()); | 	auto device = input->current_device(); | ||||||
|  | 	if(device) | ||||||
|  | 	    info.GetReturnValue().Set(Nan::LocalString(device->id())); | ||||||
|  | 	else | ||||||
|  |         info.GetReturnValue().Set(Nan::Undefined()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(AudioRecorderWrapper::_set_device) { | NAN_METHOD(AudioRecorderWrapper::_set_device) { | ||||||
| 	auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder()); | 	auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder()); | ||||||
| 	auto input = handle->_input; | 	auto input = handle->_input; | ||||||
| 
 | 
 | ||||||
| 	if(info.Length() != 2 || !info[0]->IsNumber() || !info[1]->IsFunction()) { | 	const auto is_null_device = info[0]->IsNullOrUndefined(); | ||||||
|  | 	if(info.Length() != 2 || !(is_null_device || info[0]->IsString()) || !info[1]->IsFunction()) { | ||||||
| 		Nan::ThrowError("invalid arguments"); | 		Nan::ThrowError("invalid arguments"); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auto device_id = info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0); | 	if(!audio::initialized()) { | ||||||
|  | 	    Nan::ThrowError("audio hasn't been initialized yet"); | ||||||
|  | 	    return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto device = is_null_device ? nullptr : audio::find_device_by_id(*Nan::Utf8String(info[0]), true); | ||||||
|  |     if(!device && !is_null_device) { | ||||||
|  |         Nan::ThrowError("invalid device id"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	unique_ptr<Nan::Persistent<v8::Function>> _callback = make_unique<Nan::Persistent<v8::Function>>(info[1].As<v8::Function>()); | 	unique_ptr<Nan::Persistent<v8::Function>> _callback = make_unique<Nan::Persistent<v8::Function>>(info[1].As<v8::Function>()); | ||||||
| 	unique_ptr<Nan::Persistent<v8::Object>> _recorder = make_unique<Nan::Persistent<v8::Object>>(info.Holder()); | 	unique_ptr<Nan::Persistent<v8::Object>> _recorder = make_unique<Nan::Persistent<v8::Object>>(info.Holder()); | ||||||
| 
 | 
 | ||||||
| 	auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)](bool result, std::string error) mutable { | 	auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)] { | ||||||
| 		Nan::HandleScope scope; | 		Nan::HandleScope scope; | ||||||
| 		auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate()); | 		auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate()); | ||||||
| 
 | 
 | ||||||
| 		v8::Local<v8::Value> argv[1]; |         (void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); | ||||||
| 		if(result) |  | ||||||
| 			argv[0] = v8::Boolean::New(Nan::GetCurrentContext()->GetIsolate(), result); |  | ||||||
| 		else |  | ||||||
| 			argv[0] = Nan::NewOneByteString((uint8_t*) error.data(), error.length()).ToLocalChecked(); |  | ||||||
| 		callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv); |  | ||||||
| 
 | 
 | ||||||
| 		recorder->Reset(); | 		recorder->Reset(); | ||||||
| 		call->Reset(); | 		call->Reset(); | ||||||
| 	}).option_destroyed_execute(true); | 	}).option_destroyed_execute(true); | ||||||
| 
 | 
 | ||||||
| 	std::thread([_async_callback, input, device_id]{ | 	std::thread([_async_callback, input, device]{ | ||||||
| 		string error; | 		input->set_device(device); | ||||||
| 		auto flag = input->open_device(error, device_id); | 		_async_callback(); | ||||||
| 		_async_callback(std::forward<bool>(flag), std::forward<std::string>(error)); |  | ||||||
| 	}).detach(); | 	}).detach(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -173,9 +186,13 @@ NAN_METHOD(AudioRecorderWrapper::_start) { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|     auto input = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder())->_input; |     auto input = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder())->_input; | ||||||
|  | 	std::string error{}; | ||||||
| 
 | 
 | ||||||
|     v8::Local<v8::Value> argv[1]; |     v8::Local<v8::Value> argv[1]; | ||||||
|     argv[0] = v8::Boolean::New(Nan::GetCurrentContext()->GetIsolate(), input->record()); |     if(input->record(error)) | ||||||
|  |         argv[0] = Nan::New<v8::Boolean>(true); | ||||||
|  |     else | ||||||
|  |         argv[0] = Nan::LocalString(error); | ||||||
|     (void) info[0].As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv); |     (void) info[0].As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <event2/thread.h> | #include <event2/thread.h> | ||||||
| #include <misc/digest.h> | #include <misc/digest.h> | ||||||
|  | #include <NanStrings.h> | ||||||
| 
 | 
 | ||||||
| #include "logger.h" | #include "logger.h" | ||||||
| #include "include/NanException.h" | #include "include/NanException.h" | ||||||
| @ -15,7 +16,7 @@ | |||||||
| #include "connection/ft/FileTransferManager.h" | #include "connection/ft/FileTransferManager.h" | ||||||
| #include "connection/ft/FileTransferObject.h" | #include "connection/ft/FileTransferObject.h" | ||||||
| #include "audio/AudioOutput.h" | #include "audio/AudioOutput.h" | ||||||
| #include "audio/AudioDevice.h" | #include "audio/driver/AudioDriver.h" | ||||||
| #include "audio/js/AudioOutputStream.h" | #include "audio/js/AudioOutputStream.h" | ||||||
| #include "audio/js/AudioPlayer.h" | #include "audio/js/AudioPlayer.h" | ||||||
| #include "audio/js/AudioRecorder.h" | #include "audio/js/AudioRecorder.h" | ||||||
| @ -104,11 +105,7 @@ NAN_MODULE_INIT(init) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 	string error; | 	string error; | ||||||
| 	//TODO here
 |     tc::audio::initialize(); //TODO: Notify JS when initialized?
 | ||||||
| 	//PaJack_SetClientName("TeaClient");
 |  | ||||||
| 	Pa_Initialize(); |  | ||||||
| 
 |  | ||||||
| 	std::thread(audio::devices).detach(); /* cache the devices */ |  | ||||||
| 
 | 
 | ||||||
| 	logger::info(category::general, "Loading crypt modules"); | 	logger::info(category::general, "Loading crypt modules"); | ||||||
| 	std::string descriptors = "LTGE"; | 	std::string descriptors = "LTGE"; | ||||||
| @ -141,19 +138,31 @@ NAN_MODULE_INIT(init) { | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 	tc::audio::init_event_loops(); | 	tc::audio::init_event_loops(); | ||||||
| 	/* TODO: Test error codes and make the audi playback device configurable  */ | 	tc::audio::initialize([]{ | ||||||
| 	global_audio_output = new tc::audio::AudioOutput(2, 48000); //48000 44100
 | 	    std::string error{}; | ||||||
| 	if(!global_audio_output->open_device(error, Pa_GetDefaultOutputDevice())) { | 
 | ||||||
| 		logger::error(category::audio, "Failed to initialize default audio playback: {}", error); |         std::shared_ptr<tc::audio::AudioDevice> default_output{}; | ||||||
| 	} else { | 	    for(auto& device : tc::audio::devices()) { | ||||||
| 		if(!global_audio_output->playback()) { | 	        if(device->is_output_default()) { | ||||||
| 			logger::error(category::audio, "Failed to start audio playback"); |                 default_output = device; | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
| 	    } | 	    } | ||||||
| 
 | 
 | ||||||
|  |         /* TODO: Test error codes and make the audi playback device configurable  */ | ||||||
|  |         global_audio_output = new tc::audio::AudioOutput(2, 48000); | ||||||
|  |         global_audio_output->set_device(default_output); | ||||||
|  |         if(!global_audio_output->playback(error)) { | ||||||
|  |             logger::error(category::audio, "Failed to start audio playback: {}", error); | ||||||
|  |         } | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	{ | 	{ | ||||||
| 		auto namespace_audio = Nan::New<v8::Object>(); | 		auto namespace_audio = Nan::New<v8::Object>(); | ||||||
| 		Nan::Set(namespace_audio, Nan::New<v8::String>("available_devices").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::available_devices)).ToLocalChecked()); | 		Nan::Set(namespace_audio, Nan::LocalString("available_devices"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::available_devices)).ToLocalChecked()); | ||||||
|  |         Nan::Set(namespace_audio, Nan::LocalString("initialize"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::await_initialized_js)).ToLocalChecked()); | ||||||
|  |         Nan::Set(namespace_audio, Nan::LocalString("initialized"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::initialized_js)).ToLocalChecked()); | ||||||
|  | 
 | ||||||
|         { |         { | ||||||
| 			auto namespace_playback = Nan::New<v8::Object>(); | 			auto namespace_playback = Nan::New<v8::Object>(); | ||||||
| 			audio::player::init_js(namespace_playback); | 			audio::player::init_js(namespace_playback); | ||||||
|  | |||||||
| @ -211,8 +211,8 @@ VoiceClientWrap::~VoiceClientWrap() {} | |||||||
| VoiceClient::VoiceClient(const std::shared_ptr<VoiceConnection>&, uint16_t client_id) : _client_id(client_id) { | VoiceClient::VoiceClient(const std::shared_ptr<VoiceConnection>&, uint16_t client_id) : _client_id(client_id) { | ||||||
| 	this->output_source = global_audio_output->create_source(); | 	this->output_source = global_audio_output->create_source(); | ||||||
| 	this->output_source->overflow_strategy = audio::overflow_strategy::ignore; | 	this->output_source->overflow_strategy = audio::overflow_strategy::ignore; | ||||||
| 	this->output_source->max_latency = (size_t) ceil(this->output_source->sample_rate * 0.5); | 	this->output_source->max_buffered_samples = (size_t) ceil(this->output_source->sample_rate * 0.5); | ||||||
| 	this->output_source->min_buffer = (size_t) ceil(this->output_source->sample_rate * 0.04); | 	this->output_source->min_buffered_samples = (size_t) ceil(this->output_source->sample_rate * 0.04); | ||||||
| 
 | 
 | ||||||
| 	this->output_source->on_underflow = [&]{ | 	this->output_source->on_underflow = [&]{ | ||||||
| 		if(this->_state == state::stopping) | 		if(this->_state == state::stopping) | ||||||
| @ -533,8 +533,9 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch | |||||||
| 					if(replay_head->reset_decoder) | 					if(replay_head->reset_decoder) | ||||||
| 						audio_codec.converter->reset_decoder(); | 						audio_codec.converter->reset_decoder(); | ||||||
| 
 | 
 | ||||||
|  | 					//TODO: Use statically allocated buffer?
 | ||||||
| 					auto decoded = this->decode_buffer(audio_codec.codec, replay_head->buffer); | 					auto decoded = this->decode_buffer(audio_codec.codec, replay_head->buffer); | ||||||
| 					this->output_source->enqueue_samples(decoded); | 					this->output_source->enqueue_samples(decoded->sample_data, decoded->sample_size); | ||||||
| 					this->set_state(state::playing); | 					this->set_state(state::playing); | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| #include "FileTransferManager.h" | #include "FileTransferManager.h" | ||||||
| #include "FileTransferObject.h" | #include "FileTransferObject.h" | ||||||
| #include <NanGet.h> |  | ||||||
| 
 | 
 | ||||||
| #include <misc/net.h> | #include <misc/net.h> | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| @ -593,6 +592,7 @@ void FileTransferManager::remove_transfer(tc::ft::Transfer *transfer) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #ifdef NODEJS_API | #ifdef NODEJS_API | ||||||
|  | #include <NanGet.h> | ||||||
| 
 | 
 | ||||||
| NAN_MODULE_INIT(JSTransfer::Init) { | NAN_MODULE_INIT(JSTransfer::Init) { | ||||||
| 	auto klass = Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance); | 	auto klass = Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance); | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <NanGet.h> |  | ||||||
| #include "logger.h" | #include "logger.h" | ||||||
| 
 | 
 | ||||||
| /* Basic */ | /* Basic */ | ||||||
| @ -9,6 +8,7 @@ void force_log_raw(logger::category::value, spdlog::level::level_enum level, con | |||||||
| void force_log_node(logger::category::value, spdlog::level::level_enum, const std::string_view &); | void force_log_node(logger::category::value, spdlog::level::level_enum, const std::string_view &); | ||||||
| 
 | 
 | ||||||
| #ifdef NODEJS_API | #ifdef NODEJS_API | ||||||
|  | #include <NanGet.h> | ||||||
| #include <include/NanEventCallback.h> | #include <include/NanEventCallback.h> | ||||||
| #include <include/NanStrings.h> | #include <include/NanStrings.h> | ||||||
| 
 | 
 | ||||||
| @ -109,7 +109,6 @@ void force_log_node(logger::category::value category, spdlog::level::level_enum | |||||||
| 	} | 	} | ||||||
| 	log_messages_callback(); | 	log_messages_callback(); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| void logger::initialize_raw() { | void logger::initialize_raw() { | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								native/serverconnection/src/ring_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								native/serverconnection/src/ring_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | //
 | ||||||
|  | // Created by wolverindev on 07.02.20.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include "ring_buffer.h" | ||||||
|  | #include <soundio/soundio.h> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | using namespace tc; | ||||||
|  | 
 | ||||||
|  | ring_buffer::ring_buffer(size_t cap) { | ||||||
|  |     this->handle = soundio_ring_buffer_create(nullptr, cap); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ring_buffer::~ring_buffer() { | ||||||
|  |     soundio_ring_buffer_destroy((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t ring_buffer::capacity() const { | ||||||
|  |     return soundio_ring_buffer_capacity((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t ring_buffer::free_count() const { | ||||||
|  |     return soundio_ring_buffer_free_count((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t ring_buffer::fill_count() const { | ||||||
|  |     return soundio_ring_buffer_fill_count((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | char* ring_buffer::write_ptr() { | ||||||
|  |     return soundio_ring_buffer_write_ptr((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ring_buffer::advance_write_ptr(size_t bytes) { | ||||||
|  |     soundio_ring_buffer_advance_write_ptr((SoundIoRingBuffer*) this->handle, bytes); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const void* ring_buffer::read_ptr() const { | ||||||
|  |     return soundio_ring_buffer_read_ptr((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ring_buffer::advance_read_ptr(size_t bytes) { | ||||||
|  |     soundio_ring_buffer_advance_read_ptr((SoundIoRingBuffer*) this->handle, bytes); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ring_buffer::clear() { | ||||||
|  |     soundio_ring_buffer_clear((SoundIoRingBuffer*) this->handle); | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								native/serverconnection/src/ring_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								native/serverconnection/src/ring_buffer.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <cstddef> | ||||||
|  | 
 | ||||||
|  | namespace tc { | ||||||
|  |     class ring_buffer { | ||||||
|  |         public: | ||||||
|  |             /* Attention: Actual size may be larger than given capacity */ | ||||||
|  |             explicit ring_buffer(size_t /* minimum capacity */); | ||||||
|  |             ~ring_buffer(); | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] size_t capacity() const; | ||||||
|  |             [[nodiscard]] size_t fill_count() const; | ||||||
|  |             [[nodiscard]] size_t free_count() const; | ||||||
|  | 
 | ||||||
|  |             /* do not write more than the capacity! */ | ||||||
|  |             char* write_ptr(); | ||||||
|  |             void advance_write_ptr(size_t /* count */); | ||||||
|  | 
 | ||||||
|  |             /* do not read more than the capacity! */ | ||||||
|  |             [[nodiscard]] const void* read_ptr() const; | ||||||
|  |             void advance_read_ptr(size_t /* count */); | ||||||
|  | 
 | ||||||
|  |             void clear(); | ||||||
|  |         private: | ||||||
|  |             void* handle{nullptr}; | ||||||
|  |     }; | ||||||
|  | } | ||||||
| @ -1,17 +1,14 @@ | |||||||
| #include <stdio.h> |  | ||||||
| #include <math.h> |  | ||||||
| #include <cstring> | #include <cstring> | ||||||
| #include <string> | #include <string> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <thread> | #include <thread> | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <deque> |  | ||||||
| 
 | 
 | ||||||
| #include "../../src/audio/AudioOutput.h" | #include "../../src/audio/AudioOutput.h" | ||||||
| #include "../../src/audio/AudioInput.h" | #include "../../src/audio/AudioInput.h" | ||||||
| #include "../../src/audio/filter/FilterVad.h" | #include "../../src/audio/filter/FilterVad.h" | ||||||
| #include "../../src/audio/filter/FilterThreshold.h" | #include "../../src/audio/filter/FilterThreshold.h" | ||||||
| #include "../../src/audio/Audio.h" | #include "../../src/logger.h" | ||||||
| 
 | 
 | ||||||
| #ifdef WIN32 | #ifdef WIN32 | ||||||
| 	#include <windows.h> | 	#include <windows.h> | ||||||
| @ -21,34 +18,51 @@ using namespace std; | |||||||
| using namespace tc; | using namespace tc; | ||||||
| 
 | 
 | ||||||
| int main() { | int main() { | ||||||
| 	string error; |     std::string error{}; | ||||||
| 	if(!tc::audio::initialize(error)) { | 
 | ||||||
| 		cerr << "Failed to initialize audio: " << error << endl; |     Pa_Initialize(); | ||||||
| 		return 1; | 
 | ||||||
|  |     logger::initialize_raw(); | ||||||
|  |     tc::audio::initialize(); | ||||||
|  |     tc::audio::await_initialized(); | ||||||
|  | 
 | ||||||
|  |     std::shared_ptr<tc::audio::AudioDevice> default_playback{nullptr}, default_record{nullptr}; | ||||||
|  |     for(auto& device : tc::audio::devices()) { | ||||||
|  |         if(device->is_output_default()) | ||||||
|  |             default_playback = device; | ||||||
|  |         if(device->is_input_default()) | ||||||
|  |             default_record = device; | ||||||
|     } |     } | ||||||
| 	auto playback_manager = audio::AudioOutput(2, 48000); |     assert(default_record); | ||||||
| 	if(!playback_manager.open_device(error, Pa_GetDefaultOutputDevice())) { |     assert(default_playback); | ||||||
|  | 
 | ||||||
|  |     for(auto& dev : tc::audio::devices()) { | ||||||
|  |         if(!dev->is_input_supported()) continue; | ||||||
|  | 
 | ||||||
|  |         auto playback_manager = std::make_unique<audio::AudioOutput>(2, 48000); | ||||||
|  |         if(!playback_manager->set_device(error, default_playback)) { | ||||||
|             cerr << "Failed to open output device (" << error << ")" << endl; |             cerr << "Failed to open output device (" << error << ")" << endl; | ||||||
|             return 1; |             return 1; | ||||||
|         } |         } | ||||||
| 	if(!playback_manager.playback()) { |         if(!playback_manager->playback()) { | ||||||
|             cerr << "failed to start playback" << endl; |             cerr << "failed to start playback" << endl; | ||||||
|             return 1; |             return 1; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 	auto input = audio::AudioInput(2, 48000); |         auto input = std::make_unique<audio::AudioInput>(2, 48000); | ||||||
| 	if(!input.open_device(error, Pa_GetDefaultInputDevice())) { |         if(!input->set_device(error, dev)) { | ||||||
| 		cerr << "Failed to open input device (" << error << ")" << endl; |             cerr << "Failed to open input device (" << error << "): " << dev->id() << " (" << dev->name() << ")" << endl; | ||||||
| 		return 1; |             continue; | ||||||
|         } |         } | ||||||
| 	if(!input.record()) { |         if(!input->record()) { | ||||||
| 		cerr << "failed to start record" << endl; |             cerr << "failed to start record for " << dev->id() << " (" << dev->name() << ")" << endl; | ||||||
| 		return 1; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         { |         { | ||||||
| 		auto consumer = input.create_consumer(960); |             auto target_stream = playback_manager->create_source(); | ||||||
| 		auto target_stream = playback_manager.create_source(); | 
 | ||||||
|  |             auto consumer = input->create_consumer(960); | ||||||
|             auto vad_handler = make_shared<audio::filter::VadFilter>(2, 48000, 960); |             auto vad_handler = make_shared<audio::filter::VadFilter>(2, 48000, 960); | ||||||
|             if(!vad_handler->initialize(error, 3, 4)) { |             if(!vad_handler->initialize(error, 3, 4)) { | ||||||
|                 cerr << "failed to initialize vad handler (" << error << ")"; |                 cerr << "failed to initialize vad handler (" << error << ")"; | ||||||
| @ -61,21 +75,28 @@ int main() { | |||||||
|                 return 1; |                 return 1; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| 		consumer->on_read = [target_stream, vad_handler, threshold_filter](const void* buffer, size_t samples) { |             consumer->on_read = [target_stream, vad_handler, threshold_filter](const void* buffer, size_t samples) { //Do not capture consumer!
 | ||||||
|                 target_stream->enqueue_samples(buffer, samples); |                 target_stream->enqueue_samples(buffer, samples); | ||||||
| 
 | 
 | ||||||
| 			cout << "T: " << threshold_filter->analyze(buffer, 0) << endl; |                 /*
 | ||||||
|  |                 const auto analize_value = threshold_filter->analyze(buffer, 0); | ||||||
|                 if(vad_handler->process(buffer)) { |                 if(vad_handler->process(buffer)) { | ||||||
| 				cout << "Read " << samples << endl; |                     cout << "Read " << samples << " (" << analize_value << ")" << endl; | ||||||
|                     target_stream->enqueue_samples(buffer, samples); |                     target_stream->enqueue_samples(buffer, samples); | ||||||
|                 } else { |                 } else { | ||||||
| 				cout << "Drop " << samples << endl; |                     cout << "Drop " << samples << " (" << analize_value << ")" << endl; | ||||||
|                 } |                 } | ||||||
|  |                  */ | ||||||
|             }; |             }; | ||||||
|  | 
 | ||||||
|  |             input.release(); | ||||||
|             cout << "Read started" << endl; |             cout << "Read started" << endl; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 	this_thread::sleep_for(chrono::seconds(60)); |         playback_manager.release(); //FIXME: Memory leak!
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 	this_thread::sleep_for(chrono::seconds(360)); | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	while(true) { | 	while(true) { | ||||||
|  | |||||||
| @ -1,13 +1,16 @@ | |||||||
| #include <soundio/soundio.h> | #include <soundio/soundio.h> | ||||||
| 
 | 
 | ||||||
|  | #include <cmath> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <stdio.h> |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <string> | #include <string> | ||||||
| #include <math.h> |  | ||||||
| #include <chrono> | #include <chrono> | ||||||
|  | #include <mutex> | ||||||
|  | #include "../../src/logger.h" | ||||||
|  | #include "../../src/audio/driver/SoundIO.h" | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
|  | using namespace tc::audio; | ||||||
|  | 
 | ||||||
| static const float PI = 3.1415926535f; | static const float PI = 3.1415926535f; | ||||||
| static float seconds_offset = 0.0f; | static float seconds_offset = 0.0f; | ||||||
| 
 | 
 | ||||||
| @ -63,8 +66,27 @@ static void write_callback(struct SoundIoOutStream *outstream, | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||||
| 	int err; | 	logger::initialize_raw(); | ||||||
|  | 	SoundIOBackendHandler::initialize_all(); | ||||||
|  |     SoundIOBackendHandler::connect_all(); | ||||||
| 
 | 
 | ||||||
|  |     const auto print_device = [](const std::shared_ptr<SoundIODevice>& device) { | ||||||
|  |         std::cout << "  - " << device->id() << " (" << device->name() << ")"; | ||||||
|  |         if(device->is_output_default() || device->is_input_default()) | ||||||
|  |             std::cout << " [Default]"; | ||||||
|  |         std::cout << "\n"; | ||||||
|  |     }; | ||||||
|  | 	for(auto& backend : tc::audio::SoundIOBackendHandler::all_backends()) { | ||||||
|  | 	    std::cout << "Backend " << backend->name() << ":\n"; | ||||||
|  |         std::cout << " Input devices: (" << backend->input_devices().size() << "): \n"; | ||||||
|  |         for(auto& device : backend->input_devices()) print_device(device); | ||||||
|  |         std::cout << " Output devices: (" << backend->input_devices().size() << "): \n"; | ||||||
|  |         for(auto& device : backend->input_devices()) print_device(device); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  |     SoundIOBackendHandler::shutdown_all(); | ||||||
|  | 	return 0; | ||||||
|  |     int err; | ||||||
| 	struct SoundIo *soundio = soundio_create(); | 	struct SoundIo *soundio = soundio_create(); | ||||||
| 	if (!soundio) { | 	if (!soundio) { | ||||||
| 		fprintf(stderr, "out of memory\n"); | 		fprintf(stderr, "out of memory\n"); | ||||||
| @ -85,6 +107,7 @@ int main(int argc, char **argv) { | |||||||
| 	for(int i = 0; i < soundio_input_device_count(soundio); i++) { | 	for(int i = 0; i < soundio_input_device_count(soundio); i++) { | ||||||
| 		auto dev = soundio_get_input_device(soundio, i); | 		auto dev = soundio_get_input_device(soundio, i); | ||||||
| 		cout << dev->name << " - " << dev->id << endl; | 		cout << dev->name << " - " << dev->id << endl; | ||||||
|  |         soundio_device_unref(dev); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,114 +1,48 @@ | |||||||
| /// <reference path="../../exports/exports.d.ts" />
 | /// <reference path="../../exports/exports.d.ts" />
 | ||||||
| console.log("HELLO WORLD"); | console.log("Starting app"); | ||||||
| module.paths.push("../../build/linux_x64"); | module.paths.push("../../build/linux_x64"); | ||||||
| module.paths.push("../../build/win32_64"); | module.paths.push("../../build/win32_64"); | ||||||
| 
 | 
 | ||||||
| //LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libasan.so.5
 |  | ||||||
| const os = require('os'); |  | ||||||
| //process.dlopen(module, '/usr/lib/x86_64-linux-gnu/libasan.so.5',
 |  | ||||||
| //    os.constants.dlopen.RTLD_NOW);
 |  | ||||||
| import * as fs from "fs"; |  | ||||||
| 
 |  | ||||||
| const original_require = require; | const original_require = require; | ||||||
| require = (module => original_require(__dirname + "/../../../build/linux_x64/" + module + ".node")) as any; | require = (module => original_require(__dirname + "/../../../build/linux_x64/" + module + ".node")) as any; | ||||||
| import * as handle from "teaclient_connection"; | import * as handle from "teaclient_connection"; | ||||||
| require = original_require; | require = original_require; | ||||||
| 
 | 
 | ||||||
| const connection_list = []; | console.dir(handle.audio); | ||||||
| const connection = handle.spawn_server_connection(); | handle.audio.initialize(() => { | ||||||
| const client_list = []; |     console.log("Audio initialized"); | ||||||
| 
 | 
 | ||||||
| console.dir(handle); |  | ||||||
|     console.log("Query devices..."); |     console.log("Query devices..."); | ||||||
|     console.log("Devices: %o", handle.audio.available_devices()); |     console.log("Devices: %o", handle.audio.available_devices()); | ||||||
|     console.log("Current playback device: %o", handle.audio.playback.current_device()); |     console.log("Current playback device: %o", handle.audio.playback.current_device()); | ||||||
| //handle.audio.playback.set_device(14);
 |  | ||||||
| //console.log("Current playback device: %o", handle.audio.playback.current_device());
 |  | ||||||
| 
 | 
 | ||||||
|     const stream = handle.audio.playback.create_stream(); |     const stream = handle.audio.playback.create_stream(); | ||||||
|     console.log("Own stream: %o", stream); |     console.log("Own stream: %o", stream); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| for(let i = 0; i < 12; i++) { |  | ||||||
|     const recorder = handle.audio.record.create_recorder(); |     const recorder = handle.audio.record.create_recorder(); | ||||||
|     for(const device of handle.audio.available_devices()) { |     const default_input = handle.audio.available_devices().find(e => e.input_default); | ||||||
|         if(!device.input_supported) |     console.log(default_input); | ||||||
|             continue; |     console.log(handle.audio.available_devices().find(e => e.device_id == handle.audio.playback.current_device())); | ||||||
| 
 | 
 | ||||||
|         if(device.name != "pulse") |     recorder.set_device(default_input.device_id, () => { | ||||||
|             continue; |  | ||||||
| 
 |  | ||||||
|         console.log("Found pulse at %o", device.device_index); |  | ||||||
|         recorder.set_device(device.device_index, () => { |  | ||||||
|             recorder.start(flag => console.log("X: " + flag)); |  | ||||||
|         const consumer = recorder.create_consumer(); |         const consumer = recorder.create_consumer(); | ||||||
|             consumer.create_filter_threshold(2); |         consumer.callback_data = buffer => { | ||||||
|  |             stream.write_data(buffer.buffer, true); | ||||||
|  |         }; | ||||||
|  |         recorder.start(result => { | ||||||
|  |             console.log("Start result: %o", result); | ||||||
|  |         }); | ||||||
|     }); |     }); | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* -1 => default device */ |  | ||||||
| const recorder = handle.audio.record.create_recorder(); |  | ||||||
| console.log("Have device: %o", recorder); |  | ||||||
| console.log("Device: %o", recorder.get_device()); |  | ||||||
| if(recorder.get_device() == -1) { |  | ||||||
|     console.log("Looking for devices"); |  | ||||||
|     for(const device of handle.audio.available_devices()) { |  | ||||||
|         if(!device.input_supported) |  | ||||||
|             continue; |  | ||||||
| 
 |  | ||||||
|         if(device.name != "pulse") |  | ||||||
|             continue; |  | ||||||
| 
 |  | ||||||
|         console.log("Found pulse at %o", device.device_index); |  | ||||||
|         recorder.set_device(device.device_index, () => {}); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| console.log("Device: %o", recorder.get_device()); |  | ||||||
| recorder.start(() => {}); |  | ||||||
| console.log("Started: %o", recorder.started()); |  | ||||||
| 
 |  | ||||||
| const consumer = recorder.create_consumer(); |  | ||||||
| 
 |  | ||||||
| { |  | ||||||
|     const filter = consumer.create_filter_threshold(.5); |  | ||||||
|     filter.set_margin_frames(10); |  | ||||||
|     /* |  | ||||||
|     filter.set_analyze_filter(value => { |  | ||||||
|         console.log(value); |  | ||||||
|     }) |  | ||||||
|     */ |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| { |  | ||||||
|     //const filter = consumer.create_filter_vad(3);
 |  | ||||||
|     //console.log("Filter name: %s; Filter level: %d; Filter margin: %d", filter.get_name(), filter.get_level(), filter.get_margin_frames());
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| { |  | ||||||
|     const consume = consumer.create_filter_state(); |  | ||||||
|     setTimeout(() => { |  | ||||||
|         console.log("Silence now!"); |  | ||||||
|         consume.set_consuming(true); |  | ||||||
| 
 |  | ||||||
|         setTimeout(() => { |  | ||||||
|             console.log("Speak now!"); |  | ||||||
|             consume.set_consuming(false); |  | ||||||
|         }, 1000); |  | ||||||
|     }, 1000); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
|     setInterval(() => { |     setInterval(() => { | ||||||
|     if("gc" in global) { |         const elements = handle.audio.available_devices().filter(e => e.input_supported); | ||||||
|         console.log("GC"); |         const dev = elements[Math.floor(Math.random() * elements.length)]; | ||||||
|         global.gc(); |         recorder.set_device(dev.device_id, () => { | ||||||
|     } |             console.log("Dev updated: %o", dev); | ||||||
| }, 1000); |  | ||||||
| 
 | 
 | ||||||
| let a_map = [consumer, recorder]; |             recorder.start(() => { | ||||||
| /* keep the object alive */ |                 console.log("Started"); | ||||||
| setTimeout(() => { |             }); | ||||||
|     connection.connected(); |         }); | ||||||
|     a_map = a_map.filter(e => true); |  | ||||||
|     }, 1000); |     }, 1000); | ||||||
|  | }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user