From 26372778208e9b744863b1a95c32a97bcab8a0d9 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Sat, 11 Jun 2016 22:12:07 +0100 Subject: [PATCH 01/11] Basic audio working. Many kinks. --- ds4drv/__main__.py | 35 ++- ds4drv/actions/__init__.py | 1 + ds4drv/actions/audio.py | 128 +++++++++++ ds4drv/audio/__init__.py | 2 + ds4drv/audio/gst_pulse_to_sbc_pipeline.py | 218 ++++++++++++++++++ ds4drv/audio/sbc_headers.py | 266 ++++++++++++++++++++++ ds4drv/backends/hidraw.py | 69 +++++- ds4drv/device.py | 28 ++- setup.py | 3 +- 9 files changed, 734 insertions(+), 16 deletions(-) create mode 100644 ds4drv/actions/audio.py create mode 100644 ds4drv/audio/__init__.py create mode 100644 ds4drv/audio/gst_pulse_to_sbc_pipeline.py create mode 100644 ds4drv/audio/sbc_headers.py diff --git a/ds4drv/__main__.py b/ds4drv/__main__.py index fa915a2..6ed1d36 100644 --- a/ds4drv/__main__.py +++ b/ds4drv/__main__.py @@ -9,10 +9,11 @@ from .daemon import Daemon from .eventloop import EventLoop from .exceptions import BackendError +from .audio import GstPulseToSBCPipeline class DS4Controller(object): - def __init__(self, index, options, dynamic=False): + def __init__(self, index, options, audio_pipeline, dynamic=False): self.index = index self.dynamic = dynamic self.logger = Daemon.logger.new_module("controller {0}".format(index)) @@ -33,6 +34,8 @@ def __init__(self, index, options, dynamic=False): if self.profiles: self.profiles.append("default") + self.audio_pipeline = audio_pipeline + self.load_options(self.options) def fire_event(self, event, *args): @@ -120,8 +123,10 @@ def exit(self, *args, error = True): self.logger.info(*args) -def create_controller_thread(index, controller_options, dynamic=False): - controller = DS4Controller(index, controller_options, dynamic=dynamic) +def create_controller_thread(index, controller_options, audio_pipeline, + dynamic=False): + controller = DS4Controller(index, controller_options, audio_pipeline, + dynamic=dynamic) thread = Thread(target=controller.run) thread.controller = controller @@ -131,8 +136,9 @@ def create_controller_thread(index, controller_options, dynamic=False): class SigintHandler(object): - def __init__(self, threads): + def __init__(self, threads, audio_pipeline): self.threads = threads + self.audio_pipeline = audio_pipeline def cleanup_controller_threads(self): for thread in self.threads: @@ -140,17 +146,23 @@ def cleanup_controller_threads(self): thread.controller.loop.stop() thread.join() + def cleanup_audio_pipeline(self): + self.audio_pipeline.stop() + def __call__(self, signum, frame): signal.signal(signum, signal.SIG_DFL) + self.cleanup_audio_pipeline() self.cleanup_controller_threads() + sys.exit(0) def main(): threads = [] + audio_pipeline = GstPulseToSBCPipeline() - sigint_handler = SigintHandler(threads) + sigint_handler = SigintHandler(threads, audio_pipeline) signal.signal(signal.SIGINT, sigint_handler) try: @@ -172,10 +184,20 @@ def main(): Daemon.fork(options.daemon_log, options.daemon_pid) for index, controller_options in enumerate(options.controllers): - thread = create_controller_thread(index + 1, controller_options) + thread = create_controller_thread( + index + 1, controller_options, audio_pipeline + ) threads.append(thread) + audio_pipeline.start() + for device in backend.devices: + print("-----") + from multiprocessing import Pool + p = Pool(processes=1) + def f(d): + print("f:", d) + p.apply_async(f, (device,)) connected_devices = [] for thread in threads: # Controller has received a fatal error, exit @@ -199,6 +221,7 @@ def main(): else: thread = create_controller_thread(len(threads) + 1, options.default_controller, + audio_pipeline, dynamic=True) threads.append(thread) diff --git a/ds4drv/actions/__init__.py b/ds4drv/actions/__init__.py index 2d37a0c..65ccab9 100644 --- a/ds4drv/actions/__init__.py +++ b/ds4drv/actions/__init__.py @@ -7,3 +7,4 @@ from . import input from . import led from . import status +from . import audio diff --git a/ds4drv/actions/audio.py b/ds4drv/actions/audio.py new file mode 100644 index 0000000..1d95363 --- /dev/null +++ b/ds4drv/actions/audio.py @@ -0,0 +1,128 @@ +from ..action import Action +from ..audio import SBCHeaders + + +class AudioCallbacks(): + callbacks = [] + + def __call__(self, buffer, data): + import os + #print("cbpid: ", os.getpid()) + for callback in self.callbacks: + callback(data) + +import os +from io import FileIO +hidraw_device = "/dev/hidraw3" +report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) +fd = FileIO(report_fd, "rb+", closefd=False) +class AudioAction(Action): + """Plays audio through the device""" + + frame_number = 0 + audio_buffer = b'' + + def setup(self, device): + self.audio_pipeline = self.controller.audio_pipeline + + if not isinstance(self.audio_pipeline.get_callback(), AudioCallbacks): + self.audio_pipeline.set_callback(AudioCallbacks()) + + self.audio_pipeline.get_callback().callbacks.append(self.play_audio) + + self.audio_pipeline.restart() + + def play_audio(self, data): + pos = 0 + sbc_headers = SBCHeaders() + + #print() + #print("ld: ", len(data)) + import os + #print("lpid:", os.getpid()) + while pos != len(data): + sbc_headers.parse_header(data) + frame_length = sbc_headers.calculate_frame_length() + #print("fl: ", frame_length) + + self.controller.device.play_audio(sbc_headers, + data[pos:pos + frame_length]) + #self.lplay_audio(None, data[pos:(pos+frame_length)]) + + pos += frame_length + #print("done") + + def lplay_audio(self, sbc_headers, data): + print() + print(len(self.audio_buffer)) + print(len(data)) + print() + if len(self.audio_buffer) + len(data) <= 448: + self.audio_buffer += data + return + print("running: ", len(self.audio_buffer)) + + rumble_weak = 0 + rumble_strong = 0 + r = 0 + g = 0 + b = 10 + crc = b'\x00\x00\x00\x00' + volume_speaker = 80 + volume_l = 60 + volume_r = 60 + unk2 = 100 + unk3 = 100 + flash_bright = 0 + flash_dark = 0 + #audio_header = b'\x24' + audio_header = b'\x24' + + + def frame_number(inc): + import struct + res = struct.pack(" 0xffff: + self.frame_number = 0 + return res + + def joy_data(): + data = [0xff,0x4,0x00] + #global volume_r,volume_unk2, unk3 + data.extend([rumble_weak,rumble_strong,r,g,b,flash_bright,flash_dark]) + data.extend([0]*8) + data.extend([volume_l,volume_r,unk2,volume_speaker,unk3]) + return data + + + def _11_report(): + data = joy_data() + data.extend([0]*(48)) + return b'\x11\xC0\x20' + bytearray(data) + crc + + try: + if self.reported_11 == True: pass + except AttributeError: + fd.write(_11_report()) + + def _17_report(audo_data): + return ( + b'\x17\x40\xA0' + + frame_number(4) + + audio_header + + audo_data + + bytearray(452 - len(audo_data)) + crc + ) + report = _17_report(self.audio_buffer) + + print(data[0]) + self.audio_buffer = data + + #if self._volume_r == 0: + # self.set_volume(60, 60, 0) + # self._control() + #self.write_report(report[0], report[1:]) + print(report) + print(len(report)) + fd.write(report) diff --git a/ds4drv/audio/__init__.py b/ds4drv/audio/__init__.py new file mode 100644 index 0000000..37c5d98 --- /dev/null +++ b/ds4drv/audio/__init__.py @@ -0,0 +1,2 @@ +from .sbc_headers import SBCHeaders +from .gst_pulse_to_sbc_pipeline import GstPulseToSBCPipeline diff --git a/ds4drv/audio/gst_pulse_to_sbc_pipeline.py b/ds4drv/audio/gst_pulse_to_sbc_pipeline.py new file mode 100644 index 0000000..ee31074 --- /dev/null +++ b/ds4drv/audio/gst_pulse_to_sbc_pipeline.py @@ -0,0 +1,218 @@ +import gi +gi.require_version('Gst', '1.0') +gi.require_version('GstBase', '1.0') +gi.require_version('Gtk', '3.0') +from gi.repository import Gst, GstBase, GObject, Gtk + +from multiprocessing import Process, Event +from threading import Thread, Event as tEvent +import subprocess + +from .sbc_headers import SBCHeaders + +Gst.init(None) +GObject.threads_init() + +class CallbackSink(GstBase.BaseSink): + __gstmetadata__ = ( + 'CustomSink', 'Sink', 'custom test sink element', 'poconbhui' + ) + __gsttemplates__ = Gst.PadTemplate.new( + 'sink', + Gst.PadDirection.SINK, + Gst.PadPresence.ALWAYS, + Gst.Caps.new_any() + ) + + def __init__(self, callback = None, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.set_callback(callback) + + def do_render(self, buffer): + data = buffer.extract_dup(0, buffer.get_size()) + self.callback(buffer, data) + return Gst.FlowReturn.OK + + def set_callback(self, callback): + if callback == None: + self.callback = lambda b, d: None + else: + self.callback = callback + + def get_callback(self): + return self.callback + + +class ProcessWithWatcher(object): + def __init__(self, target = None): + self.target = target + self.process = Process(target = target) + self.end_watch = tEvent() + + def watch_target(self): + while self.end_watch.is_set() != True: + self.process.join() + self.process = Process(target = self.target) + self.process.start() + + def process(self): + return self.process + + def start(self): + self.process.start() + self.start_watch() + + def join(self): + self.stop_watch() + self.process.join() + + def start_watch(self): + self.watchman = Thread(target = self.watch_target) + self.end_watch.clear() + self.watchman.start() + + def stop_watch(self): + self.end_watch.set() + + +class GstPulseToSBCPipeline(object): + def __init__(self, pulse_sink_name='ds4'): + self.pulse_sink_name = pulse_sink_name + self.sink = CallbackSink() + self.gtk_quit_main = Event() + + def run(self): + import os + print("fpsp:", os.getpid()) + + self.player = Gst.Pipeline.new('player') + + + self.pulse_source = Gst.ElementFactory.make( + 'pulsesrc', instance_name='pulse-source' + ) + self.pulse_source.set_property( + 'device', self.pulse_sink_name + '.monitor' + ) + + self.player.add(self.pulse_source) + + + self.pulse_buffer = Gst.ElementFactory.make( + 'queue', 'pulse-buffer' + ) + + self.player.add(self.pulse_buffer) + self.pulse_source.link(self.pulse_buffer) + + + self.pulse_resampler = Gst.ElementFactory.make( + 'audioresample', 'pulse-resampler' + ) + + self.player.add(self.pulse_resampler) + self.pulse_buffer.link(self.pulse_resampler) + + + self.pulse_resampler_caps = Gst.ElementFactory.make( + 'capsfilter', 'pulse-resampler-caps' + ) + self.pulse_resampler_caps.set_property( + 'caps', Gst.Caps.from_string("audio/x-raw, rate=32000") + ) + + self.player.add(self.pulse_resampler_caps) + self.pulse_resampler.link(self.pulse_resampler_caps) + + + self.sbc_encoder = Gst.ElementFactory.make( + 'sbcenc', 'sbc-encoder' + ) + + self.player.add(self.sbc_encoder) + self.pulse_resampler_caps.link(self.sbc_encoder) + + + self.sbc_encoder_caps = Gst.ElementFactory.make( + 'capsfilter', 'sbc-encoder-caps' + ) + self.sbc_encoder_caps.set_property( + 'caps', Gst.Caps.from_string(SBCHeaders().gst_sbc_caps()) + ) + + self.player.add(self.sbc_encoder_caps) + self.sbc_encoder.link(self.sbc_encoder_caps) + + + self.sbc_encoder_buffer = Gst.ElementFactory.make( + 'queue', 'sbc-encoder-buffer' + ) + + self.player.add(self.sbc_encoder_buffer) + self.sbc_encoder_caps.link(self.sbc_encoder_buffer) + + + self.player.add(self.sink) + self.sbc_encoder_buffer.link(self.sink) + + + + self.player.set_state(Gst.State.PLAYING) + + + print("here: ", self.gtk_quit_main.is_set()) + while self.gtk_quit_main.is_set() != True: + Gtk.main_iteration_do(False) + print("Ran gtk main") + + + def start(self): + # Create pulse source + pulse_sink_id = subprocess.check_output([ + 'pactl', 'load-module', 'module-null-sink', + 'sink_name="{}"'.format(self.pulse_sink_name), + 'sink_properties=device.description="{}"'.format( + "DualShock\ 4" + ), + ]) + self.pulse_sink_id = int(pulse_sink_id) + print("pulse sink id:", pulse_sink_id) + + import os + print("sis:", os.getpid()) + self.gst_process = ProcessWithWatcher(target = self.run) + self.gst_process.start() + print("sgsp:", self.gst_process) + + + def stop(self): + + import os + print("qid:", os.getpid()) + print("qgsp:", self.gst_process) + self.gtk_quit_main.set() + print("qran") + self.gst_process.join() + print("qgst_joined") + + subprocess.check_output([ + 'pactl', 'unload-module', str(self.pulse_sink_id) + ]) + + def restart(self): + print("starting restart") + self.gtk_quit_main.set() + print("a: ", self.gtk_quit_main.is_set()) + self.gst_process.join() + self.gst_process = ProcessWithWatcher(target = self.run) + self.gtk_quit_main.clear() + print("b: ", self.gtk_quit_main.is_set()) + self.gst_process.start() + print("restarted") + + def set_callback(self, callback): + self.sink.set_callback(callback) + + def get_callback(self): + return self.sink.get_callback() diff --git a/ds4drv/audio/sbc_headers.py b/ds4drv/audio/sbc_headers.py new file mode 100644 index 0000000..0c21ccb --- /dev/null +++ b/ds4drv/audio/sbc_headers.py @@ -0,0 +1,266 @@ +class SBCHeaders(object): + + MONO = 0 + DUAL_CHANNEL = 1 + STEREO = 2 + JOINT_STEREO = 3 + + + def __init__( + self, + sampling_frequency = 32000, + bitpool = 50, + channel_mode = None, + nrof_blocks = 16, + nrof_subbands = 8 + ): + if channel_mode == None: + channel_mode = SBCHeaders.STEREO + + self.syncword = 156 + + self.nrof_subbands = nrof_subbands + self.channel_mode = channel_mode + self.nrof_channels = 2 + if self.channel_mode == SBCHeaders.MONO: + self.nrof_channels = 1 + self.nrof_blocks = nrof_blocks + self.join = 0 + if self.channel_mode == SBCHeaders.JOINT_STEREO: + self.join = 1 + self.bitpool = bitpool + self.sampling_frequency = sampling_frequency + + + self.frame_length = None + self.bitrate = None + + + def calculate_frame_length(self): + # Calculate frame length + def ceildiv(a, b): + return -(-a // b) + + if ( + (self.channel_mode == SBCHeaders.MONO) + or (self.channel_mode == + SBCHeaders.DUAL_CHANNEL) + ): + + self.frame_length = ( + 4 + ( + 4 + * self.nrof_subbands + * self.nrof_channels + )//8 + + ceildiv( + self.nrof_blocks + * self.nrof_channels + * self.bitpool, + 8 + ) + ) + else: + self.frame_length = ( + 4 + ( + 4 + * self.nrof_subbands + * self.nrof_channels + )//8 + + ceildiv( + self.join + * self.nrof_subbands + + self.nrof_blocks + * self.bitpool, + 8 + ) + ) + + return self.frame_length + + + def calculate_bit_rate(self): + if self.frame_length == None: + self.calculate_frame_length() + + # Calculate bit rate + self.bit_rate = ( + 8 * self.frame_length * self.sampling_frequency + // self.nrof_subbands // self.nrof_blocks + ) + + return self.bit_rate + + + def gst_sbc_caps(self): + + if self.channel_mode == SBCHeaders.MONO: + channel_mode_str = "mono" + elif self.channel_mode == SBCHeaders.DUAL_CHANNEL: + channel_mode_str = "dual" + elif self.channel_mode == SBCHeaders.STEREO: + channel_mode_str = "stereo" + elif self.channel_mode == SBCHeaders.JOINT_STEREO: + channel_mode_str = "joint" + + return ( + 'audio/x-sbc, ' + + 'channels=' + str(self.nrof_channels) + ', ' + + 'rate=' + str(self.sampling_frequency) + ', ' + + 'channel-mode=' + channel_mode_str + ', ' + + 'blocks=' + str(self.nrof_blocks) + ', ' + + 'subbands=' + str(self.nrof_subbands) + ', ' + + 'bitpool=' + str(self.bitpool) + ) + + + def parse_header(self, raw_header): + # Info in SBC headers from + # https://tools.ietf.org/html/draft-ietf-avt-rtp-sbc-01#section-6.3 + + # Syncword should be 0x9C + self.syncword = raw_header[0] + + self.nrof_subbands = \ + SBCHeaders.parse_number_of_subbands( + raw_header + ) + self.channel_mode = SBCHeaders.parse_channel_mode( + raw_header + ) + self.nrof_channels = 2 + if self.channel_mode == SBCHeaders.MONO: + self.nrof_channels = 1 + self.nrof_blocks = SBCHeaders.parse_number_of_blocks( + raw_header + ) + self.join = 0 + if self.channel_mode == SBCHeaders.JOINT_STEREO: + self.join = 1 + self.nrof_subbands = \ + SBCHeaders.parse_number_of_subbands( + raw_header + ) + self.bitpool = SBCHeaders.parse_bitpool(raw_header) + self.sampling_frequency = SBCHeaders.parse_sampling( + raw_header + ) + + + def print_values(self): + # Info in SBC headers from + # https://tools.ietf.org/html/draft-ietf-avt-rtp-sbc-01#section-6.3 + + print("syncword: ", self.syncword) + + print("nrof_subbands", self.nrof_subbands) + print("channel_mode", [ + "MONO", "DUAL_CHANNEL", "STEREO", "JOINT_STEREO" + ][self.channel_mode] + ) + print("nrof_channels", self.nrof_channels) + print("nrof_blocks", self.nrof_blocks) + print("join: ", self.join) + print("nrof_subbands", self.nrof_subbands) + print("bitpool", self.bitpool) + print("sampling_frequency", self.sampling_frequency) + print("frame_length", self.frame_length) + print("bit_rate", self.bit_rate) + + + @staticmethod + def parse_sampling(raw_header): + + sf_word = raw_header[1] + + # Find sampling frequency from rightmost 2 bits + if sf_word & 0x80 == 0x80: + bit_0 = 1 + else: + bit_0 = 0 + + if sf_word & 0x40 == 0x40: + bit_1 = 1 + else: + bit_1 = 0 + + if (bit_0 == 0) and (bit_1 == 0): + sampling_frequency = 16000 + elif (bit_0 == 0) and (bit_1 == 1): + sampling_frequency = 32000 + elif (bit_0 == 1) and (bit_1 == 0): + sampling_frequency = 44100 + elif (bit_0 == 1) and (bit_1 == 1): + sampling_frequency = 48000 + + return sampling_frequency + + + @staticmethod + def parse_number_of_blocks(raw_header): + + nb_word = raw_header[1] + + if nb_word & 0x20 == 0x20: + bit_0 = 1 + else: + bit_0 = 0 + + if nb_word & 0x10 == 0x10: + bit_1 = 1 + else: + bit_1 = 0 + + + if (bit_0 == 0) and (bit_1 == 0): + number_of_blocks = 4 + elif (bit_0 == 0) and (bit_1 == 1): + number_of_blocks = 8 + elif (bit_0 == 1) and (bit_1 == 0): + number_of_blocks = 12 + elif (bit_0 == 1) and (bit_1 == 1): + number_of_blocks = 16 + + return number_of_blocks + + + @staticmethod + def parse_channel_mode(raw_header): + + ch_word = raw_header[1] + + if ch_word & 0x08 == 0x08: + bit_0 = 1 + else: + bit_0 = 0 + + if ch_word & 0x04 == 0x04: + bit_1 = 1 + else: + bit_1 = 0 + + if (bit_0 == 0) and (bit_1 == 0): + channel_mode = SBCHeaders.MONO + elif (bit_0 == 0) and (bit_1 == 1): + channel_mode = SBCHeaders.DUAL_CHANNEL + elif (bit_0 == 1) and (bit_1 == 0): + channel_mode = SBCHeaders.STEREO + elif (bit_0 == 1) and (bit_1 == 1): + channel_mode = SBCHeaders.JOINT_STEREO + + return channel_mode + + + @staticmethod + def parse_number_of_subbands(raw_header): + if raw_header[1] & 0x01 == 0x01: + number_of_subbands = 8 + else: + number_of_subbands = 4 + + return number_of_subbands + + + @staticmethod + def parse_bitpool(raw_header): + return int(raw_header[2]) diff --git a/ds4drv/backends/hidraw.py b/ds4drv/backends/hidraw.py index ae1cd04..3ada2ef 100644 --- a/ds4drv/backends/hidraw.py +++ b/ds4drv/backends/hidraw.py @@ -1,6 +1,8 @@ import fcntl import itertools import os +import struct +import signal from io import FileIO from time import sleep @@ -62,14 +64,30 @@ def read_feature_report(self, report_id, size): return fcntl.ioctl(self.fd, op, bytes(buf)) - def write_report(self, report_id, data): - if self.type == "bluetooth": - # TODO: Add a check for a kernel that supports writing - # output reports when such a kernel has been released. - return + @staticmethod + def sigalrm_handler(signum, frame): + raise TimeoutError + + def write_report(self, report_id, data, timeout = None): + #if self.type == "bluetooth": + # # TODO: Add a check for a kernel that supports writing + # # output reports when such a kernel has been released. + # return - hid = bytearray((report_id,)) - self.fd.write(hid + data) + #if timeout != None: + # old_sigalrm_handler = signal.getsignal(signal.SIGALRM) + # signal.signal(signal.SIGALRM, HidrawDS4Device.sigalrm_handler) + # signal.setitimer(self.ITIMER_REAL, timeout) + + try: + hid = bytearray((report_id,)) + self.fd.write(hid + data) + except TimeoutError: + pass + + #if timeout != None: + # signal.setitimer(self.ITIMER_REAL, 0) + # signal.signal(signal.SIGALRM, old_sigalrm_handler) def close(self): try: @@ -85,9 +103,46 @@ class HidrawBluetoothDS4Device(HidrawDS4Device): report_size = 78 valid_report_id = 0x11 + audio_buffer_size = 448 + audio_buffer = b'' + frame_number = 0 + def set_operational(self): self.read_feature_report(0x02, 37) + def increment_frame_number(self, inc): + self.frame_number += inc + if self.frame_number > 0xffff: + self.frame_number = 0 + + def play_audio(self, headers, data): + if len(self.audio_buffer) + len(data) <= self.audio_buffer_size: + self.audio_buffer += data + return + + crc = b'\x00\x00\x00\x00' + audio_header = b'\x24' + + self.increment_frame_number(4) + + report_id = 0x17 + report = ( + b'\x40\xA0' + + struct.pack("=0.3.0", "pyudev>=0.16"], classifiers=[ "Development Status :: 4 - Beta", From 48c8dbba5767d383ec908bb4a269882398032ffd Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 04:51:57 +0100 Subject: [PATCH 02/11] Added pure pulseaudio/sbc streaming. --- ds4drv/__main__.py | 48 +-- ds4drv/actions/audio.py | 126 +------- ds4drv/audio/__init__.py | 2 +- ds4drv/audio/gst_pulse_to_sbc_pipeline.py | 218 ------------- ds4drv/audio/pulseaudio_sbc_stream.cc | 368 ++++++++++++++++++++++ ds4drv/audio/pulseaudio_sbc_stream.hh | 88 ++++++ ds4drv/audio/pulseaudio_sbc_stream.i | 26 ++ ds4drv/audio/stream_reader.py | 70 ++++ ds4drv/backends/hidraw.py | 86 ++++- setup.py | 71 ++++- 10 files changed, 738 insertions(+), 365 deletions(-) delete mode 100644 ds4drv/audio/gst_pulse_to_sbc_pipeline.py create mode 100644 ds4drv/audio/pulseaudio_sbc_stream.cc create mode 100644 ds4drv/audio/pulseaudio_sbc_stream.hh create mode 100644 ds4drv/audio/pulseaudio_sbc_stream.i create mode 100644 ds4drv/audio/stream_reader.py diff --git a/ds4drv/__main__.py b/ds4drv/__main__.py index 6ed1d36..e21015b 100644 --- a/ds4drv/__main__.py +++ b/ds4drv/__main__.py @@ -9,11 +9,11 @@ from .daemon import Daemon from .eventloop import EventLoop from .exceptions import BackendError -from .audio import GstPulseToSBCPipeline +from .audio import pulseaudio_sbc_stream, StreamReader class DS4Controller(object): - def __init__(self, index, options, audio_pipeline, dynamic=False): + def __init__(self, index, options, stream_reader, dynamic=False): self.index = index self.dynamic = dynamic self.logger = Daemon.logger.new_module("controller {0}".format(index)) @@ -34,7 +34,7 @@ def __init__(self, index, options, audio_pipeline, dynamic=False): if self.profiles: self.profiles.append("default") - self.audio_pipeline = audio_pipeline + self.stream_reader = stream_reader self.load_options(self.options) @@ -123,9 +123,9 @@ def exit(self, *args, error = True): self.logger.info(*args) -def create_controller_thread(index, controller_options, audio_pipeline, +def create_controller_thread(index, controller_options, stream_reader, dynamic=False): - controller = DS4Controller(index, controller_options, audio_pipeline, + controller = DS4Controller(index, controller_options, stream_reader, dynamic=dynamic) thread = Thread(target=controller.run) @@ -136,9 +136,9 @@ def create_controller_thread(index, controller_options, audio_pipeline, class SigintHandler(object): - def __init__(self, threads, audio_pipeline): + def __init__(self, threads, stream_reader): self.threads = threads - self.audio_pipeline = audio_pipeline + self.stream_reader = stream_reader def cleanup_controller_threads(self): for thread in self.threads: @@ -146,13 +146,18 @@ def cleanup_controller_threads(self): thread.controller.loop.stop() thread.join() - def cleanup_audio_pipeline(self): - self.audio_pipeline.stop() + def cleanup_stream_reader(self): + print("stopping stream_reader") + self.stream_reader.stop() + print("joining stream_reader thread") + print("joined") def __call__(self, signum, frame): signal.signal(signum, signal.SIG_DFL) - self.cleanup_audio_pipeline() + print("Running SIGINT") + + self.cleanup_stream_reader() self.cleanup_controller_threads() sys.exit(0) @@ -160,11 +165,19 @@ def __call__(self, signum, frame): def main(): threads = [] - audio_pipeline = GstPulseToSBCPipeline() + stream_reader = StreamReader( + "ds4drv", "Test\\ ds4drv\\ sink" + ) - sigint_handler = SigintHandler(threads, audio_pipeline) + sigint_handler = SigintHandler(threads, stream_reader) signal.signal(signal.SIGINT, sigint_handler) + #while stream_reader.sbc_frames_waiting() == False: + # import time + # time.sleep(1) + stream_reader.start() + + try: options = load_options() except ValueError as err: @@ -185,19 +198,12 @@ def main(): for index, controller_options in enumerate(options.controllers): thread = create_controller_thread( - index + 1, controller_options, audio_pipeline + index + 1, controller_options, stream_reader ) threads.append(thread) - audio_pipeline.start() - for device in backend.devices: print("-----") - from multiprocessing import Pool - p = Pool(processes=1) - def f(d): - print("f:", d) - p.apply_async(f, (device,)) connected_devices = [] for thread in threads: # Controller has received a fatal error, exit @@ -221,7 +227,7 @@ def f(d): else: thread = create_controller_thread(len(threads) + 1, options.default_controller, - audio_pipeline, + stream_reader, dynamic=True) threads.append(thread) diff --git a/ds4drv/actions/audio.py b/ds4drv/actions/audio.py index 1d95363..4e19f3a 100644 --- a/ds4drv/actions/audio.py +++ b/ds4drv/actions/audio.py @@ -1,128 +1,32 @@ from ..action import Action from ..audio import SBCHeaders +from multiprocessing import RawArray, Manager +from ..audio import pulseaudio_sbc_stream - -class AudioCallbacks(): - callbacks = [] - - def __call__(self, buffer, data): - import os - #print("cbpid: ", os.getpid()) - for callback in self.callbacks: - callback(data) - -import os -from io import FileIO -hidraw_device = "/dev/hidraw3" -report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) -fd = FileIO(report_fd, "rb+", closefd=False) class AudioAction(Action): """Plays audio through the device""" - frame_number = 0 - audio_buffer = b'' + def __init__(self, *args, **kwargs): + super(AudioAction, self).__init__(*args, **kwargs) def setup(self, device): - self.audio_pipeline = self.controller.audio_pipeline - - if not isinstance(self.audio_pipeline.get_callback(), AudioCallbacks): - self.audio_pipeline.set_callback(AudioCallbacks()) + print("AudioAction Running setup") - self.audio_pipeline.get_callback().callbacks.append(self.play_audio) + self.stream_reader = self.controller.stream_reader - self.audio_pipeline.restart() + self.stream_reader.add_callback( + self.play_audio + ) - def play_audio(self, data): - pos = 0 - sbc_headers = SBCHeaders() - #print() - #print("ld: ", len(data)) - import os - #print("lpid:", os.getpid()) - while pos != len(data): - sbc_headers.parse_header(data) - frame_length = sbc_headers.calculate_frame_length() - #print("fl: ", frame_length) + def disable(self): + self.stream_reader.stop() - self.controller.device.play_audio(sbc_headers, - data[pos:pos + frame_length]) - #self.lplay_audio(None, data[pos:(pos+frame_length)]) - pos += frame_length - #print("done") - - def lplay_audio(self, sbc_headers, data): - print() - print(len(self.audio_buffer)) - print(len(data)) - print() - if len(self.audio_buffer) + len(data) <= 448: - self.audio_buffer += data + def play_audio(self, sbc_header, sbc_frame): + if not self.controller.device: return - print("running: ", len(self.audio_buffer)) - - rumble_weak = 0 - rumble_strong = 0 - r = 0 - g = 0 - b = 10 - crc = b'\x00\x00\x00\x00' - volume_speaker = 80 - volume_l = 60 - volume_r = 60 - unk2 = 100 - unk3 = 100 - flash_bright = 0 - flash_dark = 0 - #audio_header = b'\x24' - audio_header = b'\x24' - - - def frame_number(inc): - import struct - res = struct.pack(" 0xffff: - self.frame_number = 0 - return res - - def joy_data(): - data = [0xff,0x4,0x00] - #global volume_r,volume_unk2, unk3 - data.extend([rumble_weak,rumble_strong,r,g,b,flash_bright,flash_dark]) - data.extend([0]*8) - data.extend([volume_l,volume_r,unk2,volume_speaker,unk3]) - return data - - - def _11_report(): - data = joy_data() - data.extend([0]*(48)) - return b'\x11\xC0\x20' + bytearray(data) + crc - - try: - if self.reported_11 == True: pass - except AttributeError: - fd.write(_11_report()) - - def _17_report(audo_data): - return ( - b'\x17\x40\xA0' - + frame_number(4) - + audio_header - + audo_data - + bytearray(452 - len(audo_data)) + crc - ) - report = _17_report(self.audio_buffer) - print(data[0]) - self.audio_buffer = data + self.controller.device.play_audio(sbc_header, sbc_frame) - #if self._volume_r == 0: - # self.set_volume(60, 60, 0) - # self._control() - #self.write_report(report[0], report[1:]) - print(report) - print(len(report)) - fd.write(report) + return True diff --git a/ds4drv/audio/__init__.py b/ds4drv/audio/__init__.py index 37c5d98..ce9f49d 100644 --- a/ds4drv/audio/__init__.py +++ b/ds4drv/audio/__init__.py @@ -1,2 +1,2 @@ from .sbc_headers import SBCHeaders -from .gst_pulse_to_sbc_pipeline import GstPulseToSBCPipeline +from .stream_reader import StreamReader diff --git a/ds4drv/audio/gst_pulse_to_sbc_pipeline.py b/ds4drv/audio/gst_pulse_to_sbc_pipeline.py deleted file mode 100644 index ee31074..0000000 --- a/ds4drv/audio/gst_pulse_to_sbc_pipeline.py +++ /dev/null @@ -1,218 +0,0 @@ -import gi -gi.require_version('Gst', '1.0') -gi.require_version('GstBase', '1.0') -gi.require_version('Gtk', '3.0') -from gi.repository import Gst, GstBase, GObject, Gtk - -from multiprocessing import Process, Event -from threading import Thread, Event as tEvent -import subprocess - -from .sbc_headers import SBCHeaders - -Gst.init(None) -GObject.threads_init() - -class CallbackSink(GstBase.BaseSink): - __gstmetadata__ = ( - 'CustomSink', 'Sink', 'custom test sink element', 'poconbhui' - ) - __gsttemplates__ = Gst.PadTemplate.new( - 'sink', - Gst.PadDirection.SINK, - Gst.PadPresence.ALWAYS, - Gst.Caps.new_any() - ) - - def __init__(self, callback = None, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.set_callback(callback) - - def do_render(self, buffer): - data = buffer.extract_dup(0, buffer.get_size()) - self.callback(buffer, data) - return Gst.FlowReturn.OK - - def set_callback(self, callback): - if callback == None: - self.callback = lambda b, d: None - else: - self.callback = callback - - def get_callback(self): - return self.callback - - -class ProcessWithWatcher(object): - def __init__(self, target = None): - self.target = target - self.process = Process(target = target) - self.end_watch = tEvent() - - def watch_target(self): - while self.end_watch.is_set() != True: - self.process.join() - self.process = Process(target = self.target) - self.process.start() - - def process(self): - return self.process - - def start(self): - self.process.start() - self.start_watch() - - def join(self): - self.stop_watch() - self.process.join() - - def start_watch(self): - self.watchman = Thread(target = self.watch_target) - self.end_watch.clear() - self.watchman.start() - - def stop_watch(self): - self.end_watch.set() - - -class GstPulseToSBCPipeline(object): - def __init__(self, pulse_sink_name='ds4'): - self.pulse_sink_name = pulse_sink_name - self.sink = CallbackSink() - self.gtk_quit_main = Event() - - def run(self): - import os - print("fpsp:", os.getpid()) - - self.player = Gst.Pipeline.new('player') - - - self.pulse_source = Gst.ElementFactory.make( - 'pulsesrc', instance_name='pulse-source' - ) - self.pulse_source.set_property( - 'device', self.pulse_sink_name + '.monitor' - ) - - self.player.add(self.pulse_source) - - - self.pulse_buffer = Gst.ElementFactory.make( - 'queue', 'pulse-buffer' - ) - - self.player.add(self.pulse_buffer) - self.pulse_source.link(self.pulse_buffer) - - - self.pulse_resampler = Gst.ElementFactory.make( - 'audioresample', 'pulse-resampler' - ) - - self.player.add(self.pulse_resampler) - self.pulse_buffer.link(self.pulse_resampler) - - - self.pulse_resampler_caps = Gst.ElementFactory.make( - 'capsfilter', 'pulse-resampler-caps' - ) - self.pulse_resampler_caps.set_property( - 'caps', Gst.Caps.from_string("audio/x-raw, rate=32000") - ) - - self.player.add(self.pulse_resampler_caps) - self.pulse_resampler.link(self.pulse_resampler_caps) - - - self.sbc_encoder = Gst.ElementFactory.make( - 'sbcenc', 'sbc-encoder' - ) - - self.player.add(self.sbc_encoder) - self.pulse_resampler_caps.link(self.sbc_encoder) - - - self.sbc_encoder_caps = Gst.ElementFactory.make( - 'capsfilter', 'sbc-encoder-caps' - ) - self.sbc_encoder_caps.set_property( - 'caps', Gst.Caps.from_string(SBCHeaders().gst_sbc_caps()) - ) - - self.player.add(self.sbc_encoder_caps) - self.sbc_encoder.link(self.sbc_encoder_caps) - - - self.sbc_encoder_buffer = Gst.ElementFactory.make( - 'queue', 'sbc-encoder-buffer' - ) - - self.player.add(self.sbc_encoder_buffer) - self.sbc_encoder_caps.link(self.sbc_encoder_buffer) - - - self.player.add(self.sink) - self.sbc_encoder_buffer.link(self.sink) - - - - self.player.set_state(Gst.State.PLAYING) - - - print("here: ", self.gtk_quit_main.is_set()) - while self.gtk_quit_main.is_set() != True: - Gtk.main_iteration_do(False) - print("Ran gtk main") - - - def start(self): - # Create pulse source - pulse_sink_id = subprocess.check_output([ - 'pactl', 'load-module', 'module-null-sink', - 'sink_name="{}"'.format(self.pulse_sink_name), - 'sink_properties=device.description="{}"'.format( - "DualShock\ 4" - ), - ]) - self.pulse_sink_id = int(pulse_sink_id) - print("pulse sink id:", pulse_sink_id) - - import os - print("sis:", os.getpid()) - self.gst_process = ProcessWithWatcher(target = self.run) - self.gst_process.start() - print("sgsp:", self.gst_process) - - - def stop(self): - - import os - print("qid:", os.getpid()) - print("qgsp:", self.gst_process) - self.gtk_quit_main.set() - print("qran") - self.gst_process.join() - print("qgst_joined") - - subprocess.check_output([ - 'pactl', 'unload-module', str(self.pulse_sink_id) - ]) - - def restart(self): - print("starting restart") - self.gtk_quit_main.set() - print("a: ", self.gtk_quit_main.is_set()) - self.gst_process.join() - self.gst_process = ProcessWithWatcher(target = self.run) - self.gtk_quit_main.clear() - print("b: ", self.gtk_quit_main.is_set()) - self.gst_process.start() - print("restarted") - - def set_callback(self, callback): - self.sink.set_callback(callback) - - def get_callback(self): - return self.sink.get_callback() diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc new file mode 100644 index 0000000..9a1ab42 --- /dev/null +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -0,0 +1,368 @@ +#include "pulseaudio_sbc_stream.hh" + +#include +#include +#include + +#include +//#include + + +#include + + + +#define eprintf(...) fprintf(stderr, __VA_ARGS__) + + +void PulseaudioSBCStream::add_fd(int fd) { + eprintf("PulseaudioSBCStream::add_fd\n"); + this->fds.insert(fd); +} + +void PulseaudioSBCStream::remove_fd(int fd) { + this->fds.erase(fd); +} + +void PulseaudioSBCStream::stream_read_cb( + pa_stream *s, std::size_t length, void *self_v +) { + Self* self = static_cast(self_v); + + //printf("Stream write callback: Ready to write %zu bytes\n", length); + + sbc_t* sbc = &(self->audio_loop_sbc); + std::size_t sbc_frame_length = sbc_get_frame_length(sbc); + std::size_t sbc_buflen = 10*sbc_frame_length+10; + std::uint8_t sbc_buf[sbc_buflen]; + //eprintf("sb start\n"); + while(pa_stream_readable_size(s) > 0) { + //const void* data = NULL; + const char* data8 = NULL; + //char* audio_buffer8 = self->audio_buffer; + std::size_t length = 0; + + pa_stream_peek(s, reinterpret_cast(&data8), &length); + //eprintf("datalen: %zu\n", length); + + self->audio_buffer.insert( + self->audio_buffer.end(), data8, data8+length + ); + + //eprintf("audio_buffer_pos: %zu\n", self->audio_buffer_pos); + + for(std::size_t i=0; iaudio_buffer[0]), self->audio_buffer.size(), + sbc_buf, sbc_buflen, + &written + ); + + if(written > 0) { + // Write frames to supplied file descriptors + for( + FDList::iterator fd_it = self->fds.begin(); + fd_it != self->fds.end(); + fd_it++ + ) { + //eprintf("PulseaudioSBCStream:: Writing to fd\n"); + //eprintf("Written: %zu\n", written); + //eprintf("Syncword: %d\n", sbc_buf[0]); + write(*fd_it, sbc_buf, written); + } + + self->audio_buffer.erase( + self->audio_buffer.begin(), self->audio_buffer.begin() + read + ); + } + + pa_stream_drop(s); + } + //eprintf("sb end\n"); + +} + +void PulseaudioSBCStream::stream_state_cb(pa_stream *s, void *self_v) { + + //printf("stream state\n"); + pa_stream_state_t sst = pa_stream_get_state(s); + switch(sst) { + case PA_STREAM_UNCONNECTED: + //printf("psu\n"); + break; + case PA_STREAM_CREATING: + //printf("psc\n"); + break; + case PA_STREAM_TERMINATED: + //printf("pst\n"); + break; + case PA_STREAM_READY: + //printf("psr\n"); + break; + case PA_STREAM_FAILED: + //printf("psf\n"); + //printf("Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + + break; + } + + if(pa_stream_get_state(s) == PA_STREAM_READY) { + //printf("Stream Ready\n"); + } +} + + +void PulseaudioSBCStream::stream_success_cb( + pa_stream *s, int success, void* self_v +) { + //printf("Stream success cb %d\n", success); + + // TODO: Read stream, SBCenc and to ds4 callback +} + +void PulseaudioSBCStream::stream_overflow_cb(pa_stream* p, void* self_v) { + eprintf("Buffer overflow\n"); +} + +void PulseaudioSBCStream::stream_underflow_cb(pa_stream* p, void* self_v) { + eprintf("Buffer underflow\n"); +} + +void PulseaudioSBCStream::sink_info_cb( + pa_context* c, const pa_sink_info* i, int eol, void* self_v +) { + eprintf("sib %p %d\n", i, eol); + Self* self = static_cast(self_v); + + if(i && eol == 0 && i->owner_module == self->sink_module_id) { + + pa_proplist* sink_proplist = i->proplist; + eprintf("sink\n"); + eprintf("sink str: %s\n", i->name); + eprintf("sink idx: %d\n", i->index); + //printf("sink description: %s\n", i->description); + int err = pa_proplist_sets( + sink_proplist, PA_PROP_DEVICE_DESCRIPTION, "TEST AUDIO SINK" + ); + eprintf("sink: \n"); + eprintf("%s", pa_proplist_to_string(sink_proplist)); + //printf("ppserr: %d\n", err); + //pa_proplist_gets(sink_proplist, PA_PROP_DEVICE_DESCRIPTION)); + + + /* Set up stream */ + //pa_sample_spec sample_spec; + pa_sample_spec sample_spec = i->sample_spec; + //sample_spec.channels = 2; + //sample_spec.rate = 32000; + //sample_spec.format = PA_SAMPLE_S16LE; + + char samplebuf[1024]; + pa_sample_spec_snprint(samplebuf, 1024, &(i->sample_spec)); + eprintf("sampleformat %s\n", samplebuf); + eprintf("latency: %zu\n", i->latency); + + pa_proplist* proplist = pa_proplist_new(); + pa_proplist_set( + proplist, PA_PROP_DEVICE_DESCRIPTION, "Test DS4 Stream", 14 + ); + + pa_stream* stream = pa_stream_new_with_proplist( + c, "STR_DS4TEST", &sample_spec, NULL, proplist + ); + //printf("pa_stream_new() : %s\n", pa_strerror(pa_context_errno(c))); + //printf("Streamptr: %p\n", stream); + + pa_stream_set_state_callback(stream, stream_state_cb, NULL); + pa_stream_set_read_callback(stream, stream_read_cb, self_v); + + + pa_buffer_attr buffer_attr; + //memset(&buffer_attr, 0, sizeof(buffer_attr)); + buffer_attr.maxlength = (uint32_t) -1; + buffer_attr.prebuf = (uint32_t) -1; + buffer_attr.fragsize = (uint32_t) -1; + buffer_attr.tlength = (uint32_t) -1; + buffer_attr.minreq = (uint32_t) -1; + //buffer_attr.maxlength = (uint32_t) -1; + //buffer_attr.prebuf = (uint32_t) 12*data_per_sbc_frame; + //buffer_attr.fragsize = (uint32_t) data_per_sbc_frame; + //buffer_attr.tlength = (uint32_t) data_per_sbc_frame; + buffer_attr.fragsize = pa_usec_to_bytes(50, &sample_spec); + //buffer_attr.minreq = (uint32_t) data_per_sbc_frame; + + pa_stream_flags_t flags = PA_STREAM_ADJUST_LATENCY; + + //printf("in %s\n", i->name); + char device_strbuf[1024]; + snprintf(device_strbuf, 1024, "%s.monitor", i->name); + //printf("inm: %s\n", device_strbuf); + int screrr = pa_stream_connect_record( + stream, device_strbuf, &buffer_attr, flags + ); + //printf("screrr %d\n", screrr); + //printf( + // "pa_stream_connect_record(): %s\n", + // pa_strerror(pa_context_errno(c)) + //); + + pa_stream_trigger(stream, stream_success_cb, NULL); + + pa_stream_set_overflow_callback(stream, stream_overflow_cb, NULL); + pa_stream_set_underflow_callback(stream, stream_underflow_cb, NULL); + } +} + +void PulseaudioSBCStream::module_cb(pa_context* c, uint32_t idx, void* self_v) { + eprintf("PulseaudioSBCStream module_cb\n"); + Self* self = static_cast(self_v); + //printf("Module_cb: %d\n", idx); + self->sink_module_id = idx; + eprintf("Sink idx %d\n", idx); + + //return; + + pa_context_get_sink_info_list( + c, sink_info_cb, self_v + ); + + eprintf("PulseaudioSBCStream module_cb done\n"); +} + +void PulseaudioSBCStream::state_cb(pa_context* c, void* self_v) { + eprintf("PulseaudioSBCStream state_cb\n"); + //printf("Context changed\n"); + + if(pa_context_get_state(c) == PA_CONTEXT_READY) { + + Self* self = static_cast(self_v); + + char options_buf[1024]; + snprintf( + options_buf, 1024, + "sink_name=\"%s\" rate=\"%d\" " + "sink_properties=device.description=\"%s\"", + self->sink_name.c_str(), 32000, self->sink_description.c_str() + ); + eprintf("Module opts: %s\n", options_buf); + pa_context_load_module( + c, "module-null-sink", options_buf, module_cb, self_v + ); + + } + eprintf("PulseaudioSBCStream state_cb done\n"); +} + +void PulseaudioSBCStream::unload_module_success(pa_context* c, int success, void* self_v) { + eprintf("PulseaudioSBCStream unload_module_success\n"); + //pa_threaded_mainloop_api* api = api_v; + Self* self = static_cast(self_v); + + //pa_mainloop_quit(self->mainloop, 20); + + pa_threaded_mainloop_get_api(self->mainloop)->quit( + pa_threaded_mainloop_get_api(self->mainloop), 20 + ); + eprintf("PulseaudioSBCStream unload_module_success done\n"); +} + +void PulseaudioSBCStream::mainloop_sigint_handler( + pa_mainloop_api* api, pa_signal_event* e, int sig, void* self_v +) { + eprintf("PulseaudioSBCStream mainloop_sigint_handler\n"); + Self* self = static_cast(self_v); + + pa_signal_free(e); + + self->stop(); + eprintf("PulseaudioSBCStream mainloop_sigint_handler done\n"); +} + +PulseaudioSBCStream::PulseaudioSBCStream( + std::string sink_name, + std::string sink_description +): + context(NULL), + sink_name(sink_name), + sink_description(sink_description), + sink_module_id(-1), + + audio_buffer() +{ + eprintf("PulseaudioSBCStream init\n"); + + sbc_init(&(this->audio_loop_sbc), 0); + + this->audio_loop_sbc.frequency = SBC_FREQ_32000; + this->audio_loop_sbc.blocks = SBC_BLK_16; + this->audio_loop_sbc.subbands = SBC_SB_8; + this->audio_loop_sbc.mode = SBC_MODE_STEREO; + this->audio_loop_sbc.allocation = SBC_AM_LOUDNESS; + this->audio_loop_sbc.bitpool = 50; + this->audio_loop_sbc.endian = SBC_LE; + + pa_threaded_mainloop* mainloop = pa_threaded_mainloop_new(); + this->mainloop = mainloop; + + pa_proplist* proplist = pa_proplist_new(); + pa_proplist_sets( + proplist, PA_PROP_DEVICE_STRING, this->sink_name.c_str() + ); + pa_proplist_sets( + proplist, PA_PROP_DEVICE_DESCRIPTION, this->sink_description.c_str() + ); + + pa_context* c = pa_context_new_with_proplist( + pa_threaded_mainloop_get_api(mainloop), "DS4TEST", proplist + ); + + this->context = c; + + int err = pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_context_set_state_callback(c, state_cb, this); + + eprintf("PulseaudioSBCStream init done\n"); +} + +PulseaudioSBCStream::~PulseaudioSBCStream() { + eprintf("PulseaudioSBCStream destructor"); + pa_context_disconnect(this->context); + + sbc_finish(&(this->audio_loop_sbc)); + + eprintf("PulseaudioSBCStream destructor done"); +} + +void PulseaudioSBCStream::run() { + eprintf("PulseaudioSBCStream run\n"); + + //pa_signal_init(pa_threaded_mainloop_get_api(this->mainloop)); + //pa_signal_new(SIGINT, mainloop_sigint_handler, this); + + int ml_return; + //int err = pa_threaded_mainloop_run(this->mainloop, &ml_return); + int err = pa_threaded_mainloop_start(this->mainloop); + + eprintf("err: %d\n", err); + eprintf("returned %d\n", ml_return); + eprintf("PulseaudioSBCStream run done\n"); +} + +void PulseaudioSBCStream::stop() { + eprintf("PulseaudioSBCStream stop\n"); + if(this->sink_module_id > 0) { + eprintf("trying unload module\n"); + pa_context_unload_module( + this->context, this->sink_module_id, + unload_module_success, this + ); + } else { + unload_module_success(this->context, 0, this); + } + eprintf("PulseaudioSBCStream stop done\n"); +} diff --git a/ds4drv/audio/pulseaudio_sbc_stream.hh b/ds4drv/audio/pulseaudio_sbc_stream.hh new file mode 100644 index 0000000..ae16d8a --- /dev/null +++ b/ds4drv/audio/pulseaudio_sbc_stream.hh @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +class PulseaudioSBCStream { +private: + std::mutex sbc_frame_buffer_mutex; + std::atomic sbc_frames_waiting_flag; + +public: + typedef PulseaudioSBCStream Self; + + typedef std::function< + void ( + sbc_t* sbc, void* sbc_frame, size_t length + ) + > ReadSBCFrameCallback; + + pa_context* context; + pa_threaded_mainloop* mainloop; + + std::string sink_name; + std::string sink_description; + uint32_t sink_module_id; + + sbc_t audio_loop_sbc; + std::vector audio_buffer; + + typedef std::set FDList; + FDList fds; + + void add_fd(int fd); + void remove_fd(int fd); + + + static void stream_read_cb( + pa_stream *s, std::size_t length, void *self_v + ); + + static void stream_state_cb(pa_stream *s, void *self_v); + + + static void stream_success_cb( + pa_stream *s, int success, void* self_v + ); + + static void stream_overflow_cb(pa_stream* p, void* self_v); + + static void stream_underflow_cb(pa_stream* p, void* self_v); + + static void sink_info_cb( + pa_context* c, const pa_sink_info* i, int eol, void* self_v + ); + + static void module_cb(pa_context* c, uint32_t idx, void* self_v); + + static void state_cb(pa_context* c, void* self_v); + + static void unload_module_success(pa_context* c, int success, void* self_v); + + static void mainloop_sigint_handler( + pa_mainloop_api* api, pa_signal_event* e, int sig, void* self_v + ); + + PulseaudioSBCStream( + std::string sink_name, + std::string sink_description + ); + + ~PulseaudioSBCStream(); + + void run(); + + void stop(); +}; diff --git a/ds4drv/audio/pulseaudio_sbc_stream.i b/ds4drv/audio/pulseaudio_sbc_stream.i new file mode 100644 index 0000000..3743bbd --- /dev/null +++ b/ds4drv/audio/pulseaudio_sbc_stream.i @@ -0,0 +1,26 @@ +%module pulseaudio_sbc_stream + +/* + * Include SWIG typemaps for std::string +*/ +%include +%include +%include +%include + +/* + * Include files and definitions to be written verbatim to _wrap.cpp + */ +%{ +#include "audio/pulseaudio_sbc_stream.hh" +%} + +%include "audio/pulseaudio_sbc_stream.hh" + + +/* + * Make a CharArray and size_t_p type for getting SBC frame data from + * PulseaudioSBCStream::read_sbc_frame. + */ +%pointer_class(std::size_t, size_t_p); +%array_class(unsigned char, CharArray); diff --git a/ds4drv/audio/stream_reader.py b/ds4drv/audio/stream_reader.py new file mode 100644 index 0000000..0a3b91d --- /dev/null +++ b/ds4drv/audio/stream_reader.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +from . import pulseaudio_sbc_stream +from . import sbc_headers +from threading import Thread +import os +from select import epoll, EPOLLIN + + +class StreamReader(object): + def __init__(self, sink_name, sink_description): + self.sink_name = sink_name + self.sink_description = sink_description + + self.continue_reading = True + + self.buffer_max = 10000 + self.buffer = pulseaudio_sbc_stream.CharArray(self.buffer_max) + + self.callbacks = [] + + self.audio_stream = pulseaudio_sbc_stream.PulseaudioSBCStream( + self.sink_name, self.sink_description + ) + + self.read_pipe, self.write_pipe = os.pipe() + self.read_pipe_epoll = epoll() + self.read_pipe_epoll.register(self.read_pipe, EPOLLIN) + + self.audio_stream.add_fd(self.write_pipe) + + def start(self): + self.thread = Thread(target=self.run) + self.thread.start() + #print("running read_stream process done") + + def run(self): + + #print("Running audio_stream") + self.audio_stream.run() + + while self.continue_reading == True: + timeout = 1 + for fd, event in self.read_pipe_epoll.poll(timeout): + # Read the SBC header + frame_header = os.read(self.read_pipe, 10) + + # Parse and find the length of the frame + sbc_header = sbc_headers.SBCHeaders() + sbc_header.parse_header(frame_header) + sbc_len = sbc_header.calculate_frame_length() + + # Read the rese of the frame + rest_of_frame = os.read(self.read_pipe, sbc_len - 10) + + sbc_frame = frame_header + rest_of_frame + + for callback in self.callbacks: + callback(sbc_header, sbc_frame) + + self.audio_stream.stop() + + def stop(self): + self.audio_stream.stop() + self.continue_reading = False + self.thread.join() + + def add_callback(self, callback): + print("Adding callback", callback) + self.callbacks.append(callback) diff --git a/ds4drv/backends/hidraw.py b/ds4drv/backends/hidraw.py index 3ada2ef..69ebb4c 100644 --- a/ds4drv/backends/hidraw.py +++ b/ds4drv/backends/hidraw.py @@ -4,6 +4,9 @@ import struct import signal +import threading +from multiprocessing import Pool + from io import FileIO from time import sleep @@ -21,11 +24,74 @@ HIDIOCGFEATURE = lambda size: IOC_RW | (0x07 << 0) | (size << 16) +class HidrawWriter: + def __init__(self, hidraw_device, *args, **kwargs): + super(HidrawWriter, self).__init__(*args, **kwargs) + + self.hidraw_device = hidraw_device + + self.write_pool = Pool( + processes = 1, + initializer = HidrawWriter.pool_open_fds, initargs = (hidraw_device,) + ) + + @staticmethod + def pool_open_fds(hidraw_device): + HidrawWriter.report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) + HidrawWriter.fd = FileIO(HidrawWriter.report_fd, "rb+", closefd=False) + + @staticmethod + def pool_close_fds(): + HidrawWriter.fd.close() + + @staticmethod + def sigalrm_handler(signum, frame): + raise TimeoutError + + @staticmethod + def pool_write(data, timeout): + timedout = False + oserror = False + + if timeout != None: + old_sigalrm_handler = signal.getsignal(signal.SIGALRM) + signal.signal(signal.SIGALRM, HidrawWriter.sigalrm_handler) + signal.setitimer(signal.ITIMER_REAL, timeout) + + try: + HidrawWriter.fd.write(data) + except TimeoutError: + timedout = True + pass + except OSError: + oserror = True + pass + + if timeout != None: + signal.setitimer(signal.ITIMER_REAL, 0) + signal.signal(signal.SIGALRM, old_sigalrm_handler) + + return timedout, oserror + + def write(self, data, timeout = None): + if timeout == None: + print("No timeout") + + t, o = self.write_pool.apply(HidrawWriter.pool_write, (data, timeout)) + + def close(self): + #self.write_pool.apply(HidrawWriter.pool_close_fds); + #self.write_pool.close() + self.write_pool.terminate() + + class HidrawDS4Device(DS4Device): def __init__(self, name, addr, type, hidraw_device, event_device): try: + self.hidraw_writer = HidrawWriter(hidraw_device) self.report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) self.fd = FileIO(self.report_fd, "rb+", closefd=False) + self.input_device = InputDevice(event_device) self.input_device.grab() except (OSError, IOError) as err: @@ -33,6 +99,8 @@ def __init__(self, name, addr, type, hidraw_device, event_device): self.buf = bytearray(self.report_size) + self.threading_local = threading.local() + super(HidrawDS4Device, self).__init__(name, addr, type) def read_report(self): @@ -64,9 +132,6 @@ def read_feature_report(self, report_id, size): return fcntl.ioctl(self.fd, op, bytes(buf)) - @staticmethod - def sigalrm_handler(signum, frame): - raise TimeoutError def write_report(self, report_id, data, timeout = None): #if self.type == "bluetooth": @@ -74,23 +139,17 @@ def write_report(self, report_id, data, timeout = None): # # output reports when such a kernel has been released. # return - #if timeout != None: - # old_sigalrm_handler = signal.getsignal(signal.SIGALRM) - # signal.signal(signal.SIGALRM, HidrawDS4Device.sigalrm_handler) - # signal.setitimer(self.ITIMER_REAL, timeout) - try: hid = bytearray((report_id,)) - self.fd.write(hid + data) + #self.fd.write(hid + data) + self.hidraw_writer.write(hid + data, timeout) except TimeoutError: pass - #if timeout != None: - # signal.setitimer(self.ITIMER_REAL, 0) - # signal.signal(signal.SIGALRM, old_sigalrm_handler) def close(self): try: + self.hidraw_writer.close() self.fd.close() self.input_device.ungrab() except IOError: @@ -141,7 +200,8 @@ def play_audio(self, headers, data): self._control() maxtime = self.audio_buffer_size/headers.calculate_bit_rate() - self.write_report(report_id, report, maxtime) + #print("maxtime: ", maxtime) + self.write_report(report_id, report, timeout = maxtime) class HidrawUSBDS4Device(HidrawDS4Device): diff --git a/setup.py b/setup.py index 6e6222f..5340d9e 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,56 @@ #!/usr/bin/env python -from setuptools import setup +from setuptools import setup, Extension +import subprocess + +from setuptools.command.build_ext import build_ext as _build_ext, log +from distutils import dir_util +import os.path +class build_ext(_build_ext): + def swig_sources(self, sources, extension): + """Patch swig_sources so wrap files are put in build_tmp and + generated .py files are put in build.""" + new_sources = [] + swig_sources = [] + swig_targets = {} + + for source in sources: + (base, ext) = os.path.splitext(source) + if ext == ".i": # SWIG interface file + new_sources.append( + os.path.join(self.build_temp, base + '_wrap.cpp') + ) + swig_sources.append(source) + swig_targets[source] = new_sources[-1] + else: + new_sources.append(source) + + if not swig_sources: + return new_sources + + swig = self.swig or self.find_swig() + swig_cmd = [swig, "-python"] + swig_cmd.extend(self.swig_opts) + if self.swig_cpp: + swig_cmd.append("-c++") + + # Do not override commandline arguments + if not self.swig_opts: + for o in extension.swig_opts: + swig_cmd.append(o) + + for source in swig_sources: + target = swig_targets[source] + outdir = os.path.join(self.build_lib, os.path.dirname(source)) + + dir_util.mkpath(os.path.dirname(target)) + dir_util.mkpath(outdir) + + log.info("swigging %s to %s", source, target) + self.spawn(swig_cmd + ["-outdir", outdir, "-o", target, source]) + + return new_sources + readme = open("README.rst").read() history = open("HISTORY.rst").read() @@ -21,6 +71,25 @@ "ds4drv.backends", "ds4drv.packages", "ds4drv.audio"], + cmdclass={'build_ext': build_ext}, + ext_modules=[ + Extension( + "ds4drv.audio._pulseaudio_sbc_stream", + [ + "ds4drv/audio/pulseaudio_sbc_stream.cc", + "ds4drv/audio/pulseaudio_sbc_stream.i" + ], + swig_opts=["-c++", "-Ids4drv"], + include_dirs=["ds4drv"], + extra_compile_args=[ + subprocess.check_output( + "pkg-config --cflags libpulse", shell=True + ).decode('utf-8') + ], + libraries=["pulse", "sbc"] + ) + ], + py_modules=['ds4drv.audio.pulseaudio_sbc_stream'], install_requires=["evdev>=0.3.0", "pyudev>=0.16"], classifiers=[ "Development Status :: 4 - Beta", From 9166ec4e02c153236a5d3eb3d5c947554988e1f5 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 05:35:14 +0100 Subject: [PATCH 03/11] Removed StreamReader, pulseaudio streamer writes to pipe watched by a controller's eventloop. --- ds4drv/__main__.py | 36 +++++++------- ds4drv/actions/audio.py | 34 +++++++++---- ds4drv/audio/__init__.py | 2 +- ds4drv/audio/pulseaudio_sbc_stream.cc | 5 +- ds4drv/audio/stream_reader.py | 70 --------------------------- ds4drv/backends/hidraw.py | 2 +- 6 files changed, 49 insertions(+), 100 deletions(-) delete mode 100644 ds4drv/audio/stream_reader.py diff --git a/ds4drv/__main__.py b/ds4drv/__main__.py index e21015b..c2f2563 100644 --- a/ds4drv/__main__.py +++ b/ds4drv/__main__.py @@ -9,11 +9,11 @@ from .daemon import Daemon from .eventloop import EventLoop from .exceptions import BackendError -from .audio import pulseaudio_sbc_stream, StreamReader +from .audio import PulseaudioSBCStream class DS4Controller(object): - def __init__(self, index, options, stream_reader, dynamic=False): + def __init__(self, index, options, sbc_stream, dynamic=False): self.index = index self.dynamic = dynamic self.logger = Daemon.logger.new_module("controller {0}".format(index)) @@ -34,7 +34,7 @@ def __init__(self, index, options, stream_reader, dynamic=False): if self.profiles: self.profiles.append("default") - self.stream_reader = stream_reader + self.sbc_stream = sbc_stream self.load_options(self.options) @@ -123,9 +123,9 @@ def exit(self, *args, error = True): self.logger.info(*args) -def create_controller_thread(index, controller_options, stream_reader, +def create_controller_thread(index, controller_options, sbc_stream, dynamic=False): - controller = DS4Controller(index, controller_options, stream_reader, + controller = DS4Controller(index, controller_options, sbc_stream, dynamic=dynamic) thread = Thread(target=controller.run) @@ -136,9 +136,9 @@ def create_controller_thread(index, controller_options, stream_reader, class SigintHandler(object): - def __init__(self, threads, stream_reader): + def __init__(self, threads, sbc_stream): self.threads = threads - self.stream_reader = stream_reader + self.sbc_stream = sbc_stream def cleanup_controller_threads(self): for thread in self.threads: @@ -146,10 +146,10 @@ def cleanup_controller_threads(self): thread.controller.loop.stop() thread.join() - def cleanup_stream_reader(self): - print("stopping stream_reader") - self.stream_reader.stop() - print("joining stream_reader thread") + def cleanup_sbc_stream(self): + print("stopping sbc_stream") + self.sbc_stream.stop() + print("joining sbc_stream thread") print("joined") def __call__(self, signum, frame): @@ -157,7 +157,7 @@ def __call__(self, signum, frame): print("Running SIGINT") - self.cleanup_stream_reader() + self.cleanup_sbc_stream() self.cleanup_controller_threads() sys.exit(0) @@ -165,17 +165,17 @@ def __call__(self, signum, frame): def main(): threads = [] - stream_reader = StreamReader( + sbc_stream = PulseaudioSBCStream( "ds4drv", "Test\\ ds4drv\\ sink" ) - sigint_handler = SigintHandler(threads, stream_reader) + sigint_handler = SigintHandler(threads, sbc_stream) signal.signal(signal.SIGINT, sigint_handler) - #while stream_reader.sbc_frames_waiting() == False: + #while sbc_stream.sbc_frames_waiting() == False: # import time # time.sleep(1) - stream_reader.start() + sbc_stream.run() try: @@ -198,7 +198,7 @@ def main(): for index, controller_options in enumerate(options.controllers): thread = create_controller_thread( - index + 1, controller_options, stream_reader + index + 1, controller_options, sbc_stream ) threads.append(thread) @@ -227,7 +227,7 @@ def main(): else: thread = create_controller_thread(len(threads) + 1, options.default_controller, - stream_reader, + sbc_stream, dynamic=True) threads.append(thread) diff --git a/ds4drv/actions/audio.py b/ds4drv/actions/audio.py index 4e19f3a..b4058b8 100644 --- a/ds4drv/actions/audio.py +++ b/ds4drv/actions/audio.py @@ -3,30 +3,46 @@ from multiprocessing import RawArray, Manager from ..audio import pulseaudio_sbc_stream +import os + class AudioAction(Action): """Plays audio through the device""" - def __init__(self, *args, **kwargs): - super(AudioAction, self).__init__(*args, **kwargs) - def setup(self, device): print("AudioAction Running setup") - self.stream_reader = self.controller.stream_reader + self.sbc_stream = self.controller.sbc_stream + self.loop = self.controller.loop + + self.read_pipe, self.write_pipe = os.pipe() + + self.sbc_stream.add_fd(self.write_pipe) - self.stream_reader.add_callback( - self.play_audio - ) + self.loop.add_watcher(self.read_pipe, self.play_audio) def disable(self): - self.stream_reader.stop() + self.sbc_stream.remove_fd(self.write_pipe) - def play_audio(self, sbc_header, sbc_frame): + def play_audio(self): if not self.controller.device: return + # Read the SBC header + frame_header = os.read(self.read_pipe, 10) + + # Parse and find the length of the frame + sbc_header = SBCHeaders() + sbc_header.parse_header(frame_header) + sbc_len = sbc_header.calculate_frame_length() + + # Read the rese of the frame + rest_of_frame = os.read(self.read_pipe, sbc_len - 10) + + sbc_frame = frame_header + rest_of_frame + + # Call play_audio self.controller.device.play_audio(sbc_header, sbc_frame) return True diff --git a/ds4drv/audio/__init__.py b/ds4drv/audio/__init__.py index ce9f49d..1db883c 100644 --- a/ds4drv/audio/__init__.py +++ b/ds4drv/audio/__init__.py @@ -1,2 +1,2 @@ from .sbc_headers import SBCHeaders -from .stream_reader import StreamReader +from .pulseaudio_sbc_stream import PulseaudioSBCStream diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index 9a1ab42..f0c53e3 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -16,12 +16,15 @@ void PulseaudioSBCStream::add_fd(int fd) { - eprintf("PulseaudioSBCStream::add_fd\n"); + eprintf("PulseaudioSBCStream::add_fd %d\n", fd); this->fds.insert(fd); + eprintf("Num fds: %zu\n", this->fds.size()); } void PulseaudioSBCStream::remove_fd(int fd) { + eprintf("PulseaudioSBCStream::remove_fd %d\n", fd); this->fds.erase(fd); + eprintf("Num fds: %zu\n", this->fds.size()); } void PulseaudioSBCStream::stream_read_cb( diff --git a/ds4drv/audio/stream_reader.py b/ds4drv/audio/stream_reader.py deleted file mode 100644 index 0a3b91d..0000000 --- a/ds4drv/audio/stream_reader.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python - -from . import pulseaudio_sbc_stream -from . import sbc_headers -from threading import Thread -import os -from select import epoll, EPOLLIN - - -class StreamReader(object): - def __init__(self, sink_name, sink_description): - self.sink_name = sink_name - self.sink_description = sink_description - - self.continue_reading = True - - self.buffer_max = 10000 - self.buffer = pulseaudio_sbc_stream.CharArray(self.buffer_max) - - self.callbacks = [] - - self.audio_stream = pulseaudio_sbc_stream.PulseaudioSBCStream( - self.sink_name, self.sink_description - ) - - self.read_pipe, self.write_pipe = os.pipe() - self.read_pipe_epoll = epoll() - self.read_pipe_epoll.register(self.read_pipe, EPOLLIN) - - self.audio_stream.add_fd(self.write_pipe) - - def start(self): - self.thread = Thread(target=self.run) - self.thread.start() - #print("running read_stream process done") - - def run(self): - - #print("Running audio_stream") - self.audio_stream.run() - - while self.continue_reading == True: - timeout = 1 - for fd, event in self.read_pipe_epoll.poll(timeout): - # Read the SBC header - frame_header = os.read(self.read_pipe, 10) - - # Parse and find the length of the frame - sbc_header = sbc_headers.SBCHeaders() - sbc_header.parse_header(frame_header) - sbc_len = sbc_header.calculate_frame_length() - - # Read the rese of the frame - rest_of_frame = os.read(self.read_pipe, sbc_len - 10) - - sbc_frame = frame_header + rest_of_frame - - for callback in self.callbacks: - callback(sbc_header, sbc_frame) - - self.audio_stream.stop() - - def stop(self): - self.audio_stream.stop() - self.continue_reading = False - self.thread.join() - - def add_callback(self, callback): - print("Adding callback", callback) - self.callbacks.append(callback) diff --git a/ds4drv/backends/hidraw.py b/ds4drv/backends/hidraw.py index 69ebb4c..13c5d40 100644 --- a/ds4drv/backends/hidraw.py +++ b/ds4drv/backends/hidraw.py @@ -199,7 +199,7 @@ def play_audio(self, headers, data): self.set_volume(60, 60, 0) self._control() - maxtime = self.audio_buffer_size/headers.calculate_bit_rate() + maxtime = 0.1*self.audio_buffer_size/headers.calculate_bit_rate() #print("maxtime: ", maxtime) self.write_report(report_id, report, timeout = maxtime) From 002c578269eab79ac9700c691534bb63abc369aa Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 05:47:30 +0100 Subject: [PATCH 04/11] Set volume max volume in reports to 100. --- ds4drv/device.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ds4drv/device.py b/ds4drv/device.py index 0075b16..948c903 100644 --- a/ds4drv/device.py +++ b/ds4drv/device.py @@ -161,9 +161,9 @@ def control(self, big_rumble=0, small_rumble=0, pkt[offset+9] = min(flash_led2, 255) if report_id == 0x11: - pkt[offset+18] = min(volume_l, 255) - pkt[offset+19] = min(volume_r, 255) - pkt[offset+21] = min(volume_speaker, 255) + pkt[offset+18] = min(volume_l, 100) + pkt[offset+19] = min(volume_r, 100) + pkt[offset+21] = min(volume_speaker, 100) self.write_report(report_id, pkt) From 46f9e73c654ce09dd296d13402baa005cb8a60be Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 06:51:18 +0100 Subject: [PATCH 05/11] Removed some debug code. Made some names nicer. Quitting with ctrl-c works as expected. --- ds4drv/__main__.py | 41 +++--- ds4drv/actions/audio.py | 4 - ds4drv/audio/pulseaudio_sbc_stream.cc | 196 ++++---------------------- ds4drv/audio/pulseaudio_sbc_stream.hh | 27 +--- ds4drv/backends/hidraw.py | 52 +++---- 5 files changed, 66 insertions(+), 254 deletions(-) diff --git a/ds4drv/__main__.py b/ds4drv/__main__.py index c2f2563..d9e748f 100644 --- a/ds4drv/__main__.py +++ b/ds4drv/__main__.py @@ -136,46 +136,38 @@ def create_controller_thread(index, controller_options, sbc_stream, class SigintHandler(object): - def __init__(self, threads, sbc_stream): - self.threads = threads + def __init__(self, controller_threads, sbc_stream): + self.controller_threads = controller_threads self.sbc_stream = sbc_stream - def cleanup_controller_threads(self): - for thread in self.threads: + def cleanup_controller_controller_threads(self): + for thread in self.controller_threads: thread.controller.exit("Cleaning up...", error=False) thread.controller.loop.stop() thread.join() def cleanup_sbc_stream(self): - print("stopping sbc_stream") self.sbc_stream.stop() - print("joining sbc_stream thread") - print("joined") def __call__(self, signum, frame): signal.signal(signum, signal.SIG_DFL) - print("Running SIGINT") - self.cleanup_sbc_stream() - self.cleanup_controller_threads() + self.cleanup_controller_controller_threads() sys.exit(0) def main(): - threads = [] sbc_stream = PulseaudioSBCStream( "ds4drv", "Test\\ ds4drv\\ sink" ) + sbc_stream.run() - sigint_handler = SigintHandler(threads, sbc_stream) - signal.signal(signal.SIGINT, sigint_handler) + controller_threads = [] - #while sbc_stream.sbc_frames_waiting() == False: - # import time - # time.sleep(1) - sbc_stream.run() + sigint_handler = SigintHandler(controller_threads, sbc_stream) + signal.signal(signal.SIGINT, sigint_handler) try: @@ -200,12 +192,11 @@ def main(): thread = create_controller_thread( index + 1, controller_options, sbc_stream ) - threads.append(thread) + controller_threads.append(thread) for device in backend.devices: - print("-----") connected_devices = [] - for thread in threads: + for thread in controller_threads: # Controller has received a fatal error, exit if thread.controller.error: sys.exit(1) @@ -213,23 +204,23 @@ def main(): if thread.controller.device: connected_devices.append(thread.controller.device.device_addr) - # Clean up dynamic threads + # Clean up dynamic controller_threads if not thread.is_alive(): - threads.remove(thread) + controller_threads.remove(thread) if device.device_addr in connected_devices: backend.logger.warning("Ignoring already connected device: {0}", device.device_addr) continue - for thread in filter(lambda t: not t.controller.device, threads): + for thread in filter(lambda t: not t.controller.device, controller_threads): break else: - thread = create_controller_thread(len(threads) + 1, + thread = create_controller_thread(len(controller_threads) + 1, options.default_controller, sbc_stream, dynamic=True) - threads.append(thread) + controller_threads.append(thread) thread.controller.setup_device(device) diff --git a/ds4drv/actions/audio.py b/ds4drv/actions/audio.py index b4058b8..bedfeb2 100644 --- a/ds4drv/actions/audio.py +++ b/ds4drv/actions/audio.py @@ -9,8 +9,6 @@ class AudioAction(Action): """Plays audio through the device""" def setup(self, device): - print("AudioAction Running setup") - self.sbc_stream = self.controller.sbc_stream self.loop = self.controller.loop @@ -44,5 +42,3 @@ def play_audio(self): # Call play_audio self.controller.device.play_audio(sbc_header, sbc_frame) - - return True diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index f0c53e3..f999d11 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -11,49 +11,35 @@ #include - -#define eprintf(...) fprintf(stderr, __VA_ARGS__) - - void PulseaudioSBCStream::add_fd(int fd) { - eprintf("PulseaudioSBCStream::add_fd %d\n", fd); this->fds.insert(fd); - eprintf("Num fds: %zu\n", this->fds.size()); } void PulseaudioSBCStream::remove_fd(int fd) { - eprintf("PulseaudioSBCStream::remove_fd %d\n", fd); this->fds.erase(fd); - eprintf("Num fds: %zu\n", this->fds.size()); } -void PulseaudioSBCStream::stream_read_cb( +void PulseaudioSBCStream::read_pulse_stream( pa_stream *s, std::size_t length, void *self_v ) { Self* self = static_cast(self_v); - //printf("Stream write callback: Ready to write %zu bytes\n", length); - sbc_t* sbc = &(self->audio_loop_sbc); + std::size_t sbc_frame_length = sbc_get_frame_length(sbc); std::size_t sbc_buflen = 10*sbc_frame_length+10; std::uint8_t sbc_buf[sbc_buflen]; - //eprintf("sb start\n"); + while(pa_stream_readable_size(s) > 0) { - //const void* data = NULL; const char* data8 = NULL; - //char* audio_buffer8 = self->audio_buffer; std::size_t length = 0; pa_stream_peek(s, reinterpret_cast(&data8), &length); - //eprintf("datalen: %zu\n", length); self->audio_buffer.insert( self->audio_buffer.end(), data8, data8+length ); - //eprintf("audio_buffer_pos: %zu\n", self->audio_buffer_pos); - for(std::size_t i=0; ifds.end(); fd_it++ ) { - //eprintf("PulseaudioSBCStream:: Writing to fd\n"); - //eprintf("Written: %zu\n", written); - //eprintf("Syncword: %d\n", sbc_buf[0]); write(*fd_it, sbc_buf, written); } @@ -86,162 +69,68 @@ void PulseaudioSBCStream::stream_read_cb( pa_stream_drop(s); } - //eprintf("sb end\n"); - -} - -void PulseaudioSBCStream::stream_state_cb(pa_stream *s, void *self_v) { - - //printf("stream state\n"); - pa_stream_state_t sst = pa_stream_get_state(s); - switch(sst) { - case PA_STREAM_UNCONNECTED: - //printf("psu\n"); - break; - case PA_STREAM_CREATING: - //printf("psc\n"); - break; - case PA_STREAM_TERMINATED: - //printf("pst\n"); - break; - case PA_STREAM_READY: - //printf("psr\n"); - break; - case PA_STREAM_FAILED: - //printf("psf\n"); - //printf("Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - - break; - } - - if(pa_stream_get_state(s) == PA_STREAM_READY) { - //printf("Stream Ready\n"); - } } - -void PulseaudioSBCStream::stream_success_cb( - pa_stream *s, int success, void* self_v -) { - //printf("Stream success cb %d\n", success); - - // TODO: Read stream, SBCenc and to ds4 callback -} - -void PulseaudioSBCStream::stream_overflow_cb(pa_stream* p, void* self_v) { - eprintf("Buffer overflow\n"); -} - -void PulseaudioSBCStream::stream_underflow_cb(pa_stream* p, void* self_v) { - eprintf("Buffer underflow\n"); -} - -void PulseaudioSBCStream::sink_info_cb( +void PulseaudioSBCStream::setup_pulse_stream( pa_context* c, const pa_sink_info* i, int eol, void* self_v ) { - eprintf("sib %p %d\n", i, eol); Self* self = static_cast(self_v); if(i && eol == 0 && i->owner_module == self->sink_module_id) { pa_proplist* sink_proplist = i->proplist; - eprintf("sink\n"); - eprintf("sink str: %s\n", i->name); - eprintf("sink idx: %d\n", i->index); - //printf("sink description: %s\n", i->description); int err = pa_proplist_sets( sink_proplist, PA_PROP_DEVICE_DESCRIPTION, "TEST AUDIO SINK" ); - eprintf("sink: \n"); - eprintf("%s", pa_proplist_to_string(sink_proplist)); - //printf("ppserr: %d\n", err); - //pa_proplist_gets(sink_proplist, PA_PROP_DEVICE_DESCRIPTION)); - /* Set up stream */ - //pa_sample_spec sample_spec; - pa_sample_spec sample_spec = i->sample_spec; - //sample_spec.channels = 2; - //sample_spec.rate = 32000; - //sample_spec.format = PA_SAMPLE_S16LE; - char samplebuf[1024]; pa_sample_spec_snprint(samplebuf, 1024, &(i->sample_spec)); - eprintf("sampleformat %s\n", samplebuf); - eprintf("latency: %zu\n", i->latency); - pa_proplist* proplist = pa_proplist_new(); - pa_proplist_set( - proplist, PA_PROP_DEVICE_DESCRIPTION, "Test DS4 Stream", 14 + pa_stream* stream = pa_stream_new( + c, "" /*"STR_DS4TEST"*/, &(i->sample_spec), NULL ); - pa_stream* stream = pa_stream_new_with_proplist( - c, "STR_DS4TEST", &sample_spec, NULL, proplist - ); - //printf("pa_stream_new() : %s\n", pa_strerror(pa_context_errno(c))); - //printf("Streamptr: %p\n", stream); - - pa_stream_set_state_callback(stream, stream_state_cb, NULL); - pa_stream_set_read_callback(stream, stream_read_cb, self_v); + pa_stream_set_read_callback(stream, read_pulse_stream, self_v); pa_buffer_attr buffer_attr; - //memset(&buffer_attr, 0, sizeof(buffer_attr)); buffer_attr.maxlength = (uint32_t) -1; buffer_attr.prebuf = (uint32_t) -1; buffer_attr.fragsize = (uint32_t) -1; buffer_attr.tlength = (uint32_t) -1; buffer_attr.minreq = (uint32_t) -1; - //buffer_attr.maxlength = (uint32_t) -1; - //buffer_attr.prebuf = (uint32_t) 12*data_per_sbc_frame; - //buffer_attr.fragsize = (uint32_t) data_per_sbc_frame; - //buffer_attr.tlength = (uint32_t) data_per_sbc_frame; - buffer_attr.fragsize = pa_usec_to_bytes(50, &sample_spec); - //buffer_attr.minreq = (uint32_t) data_per_sbc_frame; + buffer_attr.fragsize = pa_usec_to_bytes(50, &(i->sample_spec)); pa_stream_flags_t flags = PA_STREAM_ADJUST_LATENCY; - //printf("in %s\n", i->name); char device_strbuf[1024]; snprintf(device_strbuf, 1024, "%s.monitor", i->name); - //printf("inm: %s\n", device_strbuf); int screrr = pa_stream_connect_record( stream, device_strbuf, &buffer_attr, flags ); - //printf("screrr %d\n", screrr); - //printf( - // "pa_stream_connect_record(): %s\n", - // pa_strerror(pa_context_errno(c)) - //); - - pa_stream_trigger(stream, stream_success_cb, NULL); - - pa_stream_set_overflow_callback(stream, stream_overflow_cb, NULL); - pa_stream_set_underflow_callback(stream, stream_underflow_cb, NULL); } } -void PulseaudioSBCStream::module_cb(pa_context* c, uint32_t idx, void* self_v) { - eprintf("PulseaudioSBCStream module_cb\n"); +void PulseaudioSBCStream::module_setup_cb( + pa_context* c, uint32_t idx, void* self_v +) { Self* self = static_cast(self_v); - //printf("Module_cb: %d\n", idx); - self->sink_module_id = idx; - eprintf("Sink idx %d\n", idx); - //return; + self->sink_module_id = idx; pa_context_get_sink_info_list( - c, sink_info_cb, self_v + c, setup_pulse_stream, self_v ); - - eprintf("PulseaudioSBCStream module_cb done\n"); } -void PulseaudioSBCStream::state_cb(pa_context* c, void* self_v) { - eprintf("PulseaudioSBCStream state_cb\n"); - //printf("Context changed\n"); +void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { + if(pa_context_get_state(c) == PA_CONTEXT_CONNECTING) { + signal(SIGINT, SIG_IGN); + } if(pa_context_get_state(c) == PA_CONTEXT_READY) { + printf("[info][PulseaudioSBCStream] Connecting to Pulseaudio\n"); Self* self = static_cast(self_v); @@ -252,38 +141,23 @@ void PulseaudioSBCStream::state_cb(pa_context* c, void* self_v) { "sink_properties=device.description=\"%s\"", self->sink_name.c_str(), 32000, self->sink_description.c_str() ); - eprintf("Module opts: %s\n", options_buf); pa_context_load_module( - c, "module-null-sink", options_buf, module_cb, self_v + c, "module-null-sink", options_buf, module_setup_cb, self_v ); } - eprintf("PulseaudioSBCStream state_cb done\n"); } -void PulseaudioSBCStream::unload_module_success(pa_context* c, int success, void* self_v) { - eprintf("PulseaudioSBCStream unload_module_success\n"); - //pa_threaded_mainloop_api* api = api_v; +void PulseaudioSBCStream::unload_module_success( + pa_context* c, int success, void* self_v +) { Self* self = static_cast(self_v); - //pa_mainloop_quit(self->mainloop, 20); - pa_threaded_mainloop_get_api(self->mainloop)->quit( pa_threaded_mainloop_get_api(self->mainloop), 20 ); - eprintf("PulseaudioSBCStream unload_module_success done\n"); -} - -void PulseaudioSBCStream::mainloop_sigint_handler( - pa_mainloop_api* api, pa_signal_event* e, int sig, void* self_v -) { - eprintf("PulseaudioSBCStream mainloop_sigint_handler\n"); - Self* self = static_cast(self_v); - - pa_signal_free(e); - self->stop(); - eprintf("PulseaudioSBCStream mainloop_sigint_handler done\n"); + printf("[info][PulseaudioSBCStream] Disconnect successful\n"); } PulseaudioSBCStream::PulseaudioSBCStream( @@ -297,8 +171,6 @@ PulseaudioSBCStream::PulseaudioSBCStream( audio_buffer() { - eprintf("PulseaudioSBCStream init\n"); - sbc_init(&(this->audio_loop_sbc), 0); this->audio_loop_sbc.frequency = SBC_FREQ_32000; @@ -327,39 +199,22 @@ PulseaudioSBCStream::PulseaudioSBCStream( this->context = c; int err = pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL); - pa_context_set_state_callback(c, state_cb, this); - - eprintf("PulseaudioSBCStream init done\n"); + pa_context_set_state_callback(c, context_state_cb, this); } PulseaudioSBCStream::~PulseaudioSBCStream() { - eprintf("PulseaudioSBCStream destructor"); pa_context_disconnect(this->context); sbc_finish(&(this->audio_loop_sbc)); - - eprintf("PulseaudioSBCStream destructor done"); } void PulseaudioSBCStream::run() { - eprintf("PulseaudioSBCStream run\n"); - - //pa_signal_init(pa_threaded_mainloop_get_api(this->mainloop)); - //pa_signal_new(SIGINT, mainloop_sigint_handler, this); - - int ml_return; - //int err = pa_threaded_mainloop_run(this->mainloop, &ml_return); int err = pa_threaded_mainloop_start(this->mainloop); - - eprintf("err: %d\n", err); - eprintf("returned %d\n", ml_return); - eprintf("PulseaudioSBCStream run done\n"); } void PulseaudioSBCStream::stop() { - eprintf("PulseaudioSBCStream stop\n"); + printf("[info][PulseaudioSBCStream] Disconnecting from Pulseaudio\n"); if(this->sink_module_id > 0) { - eprintf("trying unload module\n"); pa_context_unload_module( this->context, this->sink_module_id, unload_module_success, this @@ -367,5 +222,4 @@ void PulseaudioSBCStream::stop() { } else { unload_module_success(this->context, 0, this); } - eprintf("PulseaudioSBCStream stop done\n"); } diff --git a/ds4drv/audio/pulseaudio_sbc_stream.hh b/ds4drv/audio/pulseaudio_sbc_stream.hh index ae16d8a..e18da91 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.hh +++ b/ds4drv/audio/pulseaudio_sbc_stream.hh @@ -16,10 +16,6 @@ #include class PulseaudioSBCStream { -private: - std::mutex sbc_frame_buffer_mutex; - std::atomic sbc_frames_waiting_flag; - public: typedef PulseaudioSBCStream Self; @@ -46,35 +42,20 @@ public: void remove_fd(int fd); - static void stream_read_cb( + static void read_pulse_stream( pa_stream *s, std::size_t length, void *self_v ); - static void stream_state_cb(pa_stream *s, void *self_v); - - - static void stream_success_cb( - pa_stream *s, int success, void* self_v - ); - - static void stream_overflow_cb(pa_stream* p, void* self_v); - - static void stream_underflow_cb(pa_stream* p, void* self_v); - - static void sink_info_cb( + static void setup_pulse_stream( pa_context* c, const pa_sink_info* i, int eol, void* self_v ); - static void module_cb(pa_context* c, uint32_t idx, void* self_v); + static void module_setup_cb(pa_context* c, uint32_t idx, void* self_v); - static void state_cb(pa_context* c, void* self_v); + static void context_state_cb(pa_context* c, void* self_v); static void unload_module_success(pa_context* c, int success, void* self_v); - static void mainloop_sigint_handler( - pa_mainloop_api* api, pa_signal_event* e, int sig, void* self_v - ); - PulseaudioSBCStream( std::string sink_name, std::string sink_description diff --git a/ds4drv/backends/hidraw.py b/ds4drv/backends/hidraw.py index 13c5d40..a13798e 100644 --- a/ds4drv/backends/hidraw.py +++ b/ds4drv/backends/hidraw.py @@ -4,7 +4,7 @@ import struct import signal -import threading +import signal from multiprocessing import Pool from io import FileIO @@ -32,11 +32,16 @@ def __init__(self, hidraw_device, *args, **kwargs): self.write_pool = Pool( processes = 1, - initializer = HidrawWriter.pool_open_fds, initargs = (hidraw_device,) + initializer = HidrawWriter.pool_init, initargs = (hidraw_device,) ) @staticmethod - def pool_open_fds(hidraw_device): + def pool_init(hidraw_device): + # Signals have been inherited from the parent. In particular, + # the cleanup signals from __main__. Signals are completely ignored + # here since Pool hangs otherwise. + signal.signal(signal.SIGINT, signal.SIG_IGN) + HidrawWriter.report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) HidrawWriter.fd = FileIO(HidrawWriter.report_fd, "rb+", closefd=False) @@ -53,36 +58,30 @@ def pool_write(data, timeout): timedout = False oserror = False - if timeout != None: - old_sigalrm_handler = signal.getsignal(signal.SIGALRM) - signal.signal(signal.SIGALRM, HidrawWriter.sigalrm_handler) - signal.setitimer(signal.ITIMER_REAL, timeout) - try: + if timeout != None: + old_sigalrm_handler = signal.getsignal(signal.SIGALRM) + signal.signal(signal.SIGALRM, HidrawWriter.sigalrm_handler) + signal.setitimer(signal.ITIMER_REAL, timeout) + HidrawWriter.fd.write(data) except TimeoutError: - timedout = True pass except OSError: - oserror = True pass - if timeout != None: - signal.setitimer(signal.ITIMER_REAL, 0) - signal.signal(signal.SIGALRM, old_sigalrm_handler) - - return timedout, oserror + finally: + if timeout != None: + signal.setitimer(signal.ITIMER_REAL, 0) + signal.signal(signal.SIGALRM, old_sigalrm_handler) def write(self, data, timeout = None): - if timeout == None: - print("No timeout") - - t, o = self.write_pool.apply(HidrawWriter.pool_write, (data, timeout)) + return self.write_pool.apply(HidrawWriter.pool_write, (data, timeout)) def close(self): - #self.write_pool.apply(HidrawWriter.pool_close_fds); - #self.write_pool.close() - self.write_pool.terminate() + self.write_pool.apply(HidrawWriter.pool_close_fds) + self.write_pool.close() + self.write_pool.join() class HidrawDS4Device(DS4Device): @@ -99,8 +98,6 @@ def __init__(self, name, addr, type, hidraw_device, event_device): self.buf = bytearray(self.report_size) - self.threading_local = threading.local() - super(HidrawDS4Device, self).__init__(name, addr, type) def read_report(self): @@ -134,14 +131,8 @@ def read_feature_report(self, report_id, size): def write_report(self, report_id, data, timeout = None): - #if self.type == "bluetooth": - # # TODO: Add a check for a kernel that supports writing - # # output reports when such a kernel has been released. - # return - try: hid = bytearray((report_id,)) - #self.fd.write(hid + data) self.hidraw_writer.write(hid + data, timeout) except TimeoutError: pass @@ -200,7 +191,6 @@ def play_audio(self, headers, data): self._control() maxtime = 0.1*self.audio_buffer_size/headers.calculate_bit_rate() - #print("maxtime: ", maxtime) self.write_report(report_id, report, timeout = maxtime) From eac9d912d6ac63765640f57f1134e888d4dbe742 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 07:29:33 +0100 Subject: [PATCH 06/11] Cleaned up pulseaudio code. Added checking some error values. Documentation on what to check is poor, so I can't be sure what I've missed. --- ds4drv/audio/pulseaudio_sbc_stream.cc | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index f999d11..356de06 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -5,7 +5,6 @@ #include #include -//#include #include @@ -78,17 +77,12 @@ void PulseaudioSBCStream::setup_pulse_stream( if(i && eol == 0 && i->owner_module == self->sink_module_id) { - pa_proplist* sink_proplist = i->proplist; - int err = pa_proplist_sets( - sink_proplist, PA_PROP_DEVICE_DESCRIPTION, "TEST AUDIO SINK" - ); - /* Set up stream */ char samplebuf[1024]; pa_sample_spec_snprint(samplebuf, 1024, &(i->sample_spec)); pa_stream* stream = pa_stream_new( - c, "" /*"STR_DS4TEST"*/, &(i->sample_spec), NULL + c, self->sink_description.c_str(), &(i->sample_spec), NULL ); pa_stream_set_read_callback(stream, read_pulse_stream, self_v); @@ -106,7 +100,7 @@ void PulseaudioSBCStream::setup_pulse_stream( char device_strbuf[1024]; snprintf(device_strbuf, 1024, "%s.monitor", i->name); - int screrr = pa_stream_connect_record( + pa_stream_connect_record( stream, device_strbuf, &buffer_attr, flags ); } @@ -119,16 +113,13 @@ void PulseaudioSBCStream::module_setup_cb( self->sink_module_id = idx; - pa_context_get_sink_info_list( + pa_operation* op = pa_context_get_sink_info_list( c, setup_pulse_stream, self_v ); + pa_operation_unref(op); } void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { - if(pa_context_get_state(c) == PA_CONTEXT_CONNECTING) { - signal(SIGINT, SIG_IGN); - } - if(pa_context_get_state(c) == PA_CONTEXT_READY) { printf("[info][PulseaudioSBCStream] Connecting to Pulseaudio\n"); @@ -141,9 +132,10 @@ void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { "sink_properties=device.description=\"%s\"", self->sink_name.c_str(), 32000, self->sink_description.c_str() ); - pa_context_load_module( + pa_operation* op = pa_context_load_module( c, "module-null-sink", options_buf, module_setup_cb, self_v ); + pa_operation_unref(op); } } @@ -153,9 +145,8 @@ void PulseaudioSBCStream::unload_module_success( ) { Self* self = static_cast(self_v); - pa_threaded_mainloop_get_api(self->mainloop)->quit( - pa_threaded_mainloop_get_api(self->mainloop), 20 - ); + pa_mainloop_api* api = pa_threaded_mainloop_get_api(self->mainloop); + api->quit(api, 20); printf("[info][PulseaudioSBCStream] Disconnect successful\n"); } @@ -193,12 +184,17 @@ PulseaudioSBCStream::PulseaudioSBCStream( ); pa_context* c = pa_context_new_with_proplist( - pa_threaded_mainloop_get_api(mainloop), "DS4TEST", proplist + pa_threaded_mainloop_get_api(mainloop), + this->sink_name.c_str(), proplist ); this->context = c; int err = pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL); + if(err < 0) { + printf("[error][PulseaudioSBCStream] Error connecting context\n"); + } + pa_context_set_state_callback(c, context_state_cb, this); } @@ -210,15 +206,19 @@ PulseaudioSBCStream::~PulseaudioSBCStream() { void PulseaudioSBCStream::run() { int err = pa_threaded_mainloop_start(this->mainloop); + if(err < 0) { + printf("[error][PulseaudioSBCStream] Error starting mainloop\n"); + } } void PulseaudioSBCStream::stop() { printf("[info][PulseaudioSBCStream] Disconnecting from Pulseaudio\n"); if(this->sink_module_id > 0) { - pa_context_unload_module( + pa_operation* op = pa_context_unload_module( this->context, this->sink_module_id, unload_module_success, this ); + pa_operation_unref(op); } else { unload_module_success(this->context, 0, this); } From becd1cd81400b30c1452d133abdb777e28810291 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 07:38:28 +0100 Subject: [PATCH 07/11] Cleaned up some little bits --- ds4drv/__main__.py | 1 - ds4drv/actions/audio.py | 1 - ds4drv/audio/pulseaudio_sbc_stream.cc | 1 - ds4drv/audio/pulseaudio_sbc_stream.hh | 2 +- ds4drv/audio/sbc_headers.py | 22 ---------------------- 5 files changed, 1 insertion(+), 26 deletions(-) diff --git a/ds4drv/__main__.py b/ds4drv/__main__.py index d9e748f..8dbe5d5 100644 --- a/ds4drv/__main__.py +++ b/ds4drv/__main__.py @@ -169,7 +169,6 @@ def main(): sigint_handler = SigintHandler(controller_threads, sbc_stream) signal.signal(signal.SIGINT, sigint_handler) - try: options = load_options() except ValueError as err: diff --git a/ds4drv/actions/audio.py b/ds4drv/actions/audio.py index bedfeb2..260e9da 100644 --- a/ds4drv/actions/audio.py +++ b/ds4drv/actions/audio.py @@ -1,6 +1,5 @@ from ..action import Action from ..audio import SBCHeaders -from multiprocessing import RawArray, Manager from ..audio import pulseaudio_sbc_stream import os diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index 356de06..ec13796 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -136,7 +136,6 @@ void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { c, "module-null-sink", options_buf, module_setup_cb, self_v ); pa_operation_unref(op); - } } diff --git a/ds4drv/audio/pulseaudio_sbc_stream.hh b/ds4drv/audio/pulseaudio_sbc_stream.hh index e18da91..be2c785 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.hh +++ b/ds4drv/audio/pulseaudio_sbc_stream.hh @@ -17,7 +17,7 @@ class PulseaudioSBCStream { public: - typedef PulseaudioSBCStream Self; + typedef PulseaudioSBCStream Self; typedef std::function< void ( diff --git a/ds4drv/audio/sbc_headers.py b/ds4drv/audio/sbc_headers.py index 0c21ccb..28d77d2 100644 --- a/ds4drv/audio/sbc_headers.py +++ b/ds4drv/audio/sbc_headers.py @@ -92,28 +92,6 @@ def calculate_bit_rate(self): return self.bit_rate - def gst_sbc_caps(self): - - if self.channel_mode == SBCHeaders.MONO: - channel_mode_str = "mono" - elif self.channel_mode == SBCHeaders.DUAL_CHANNEL: - channel_mode_str = "dual" - elif self.channel_mode == SBCHeaders.STEREO: - channel_mode_str = "stereo" - elif self.channel_mode == SBCHeaders.JOINT_STEREO: - channel_mode_str = "joint" - - return ( - 'audio/x-sbc, ' - + 'channels=' + str(self.nrof_channels) + ', ' - + 'rate=' + str(self.sampling_frequency) + ', ' - + 'channel-mode=' + channel_mode_str + ', ' - + 'blocks=' + str(self.nrof_blocks) + ', ' - + 'subbands=' + str(self.nrof_subbands) + ', ' - + 'bitpool=' + str(self.bitpool) - ) - - def parse_header(self, raw_header): # Info in SBC headers from # https://tools.ietf.org/html/draft-ietf-avt-rtp-sbc-01#section-6.3 From 400cde220a41d04bef97d84ad711541bc65e27c9 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 1 Jul 2016 21:10:15 +0100 Subject: [PATCH 08/11] Added specifying format in sink and resetting sbc encoder format after sink is set up. --- ds4drv/audio/pulseaudio_sbc_stream.cc | 32 +++++++++++++++++++++------ ds4drv/backends/hidraw.py | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index ec13796..78e3465 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -77,10 +77,27 @@ void PulseaudioSBCStream::setup_pulse_stream( if(i && eol == 0 && i->owner_module == self->sink_module_id) { - /* Set up stream */ - char samplebuf[1024]; - pa_sample_spec_snprint(samplebuf, 1024, &(i->sample_spec)); + /* Fix sbc encoder format */ + if(i->sample_spec.format == PA_SAMPLE_S16BE) { + printf( + "[info][PulseaudioSBCStream] " + "Stream format s16be\n" + ); + self->audio_loop_sbc.endian = SBC_BE; + } else if(i->sample_spec.format == PA_SAMPLE_S16LE) { + printf( + "[info][PulseaudioSBCStream] " + "Stream format s16le\n" + ); + self->audio_loop_sbc.endian = SBC_LE; + } else { + printf( + "[error][PulseaudioSBCStream] " + "Unable to determine stream format\n" + ); + } + /* Set up stream */ pa_stream* stream = pa_stream_new( c, self->sink_description.c_str(), &(i->sample_spec), NULL ); @@ -128,9 +145,10 @@ void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { char options_buf[1024]; snprintf( options_buf, 1024, - "sink_name=\"%s\" rate=\"%d\" " - "sink_properties=device.description=\"%s\"", - self->sink_name.c_str(), 32000, self->sink_description.c_str() + "rate=\"%d\" format=\"%s\" channels=\"%d\"" + "sink_name=\"%s\" sink_properties=device.description=\"%s\"", + 32000, pa_sample_format_to_string(PA_SAMPLE_S16NE), 2, + self->sink_name.c_str(), self->sink_description.c_str() ); pa_operation* op = pa_context_load_module( c, "module-null-sink", options_buf, module_setup_cb, self_v @@ -169,7 +187,7 @@ PulseaudioSBCStream::PulseaudioSBCStream( this->audio_loop_sbc.mode = SBC_MODE_STEREO; this->audio_loop_sbc.allocation = SBC_AM_LOUDNESS; this->audio_loop_sbc.bitpool = 50; - this->audio_loop_sbc.endian = SBC_LE; + this->audio_loop_sbc.endian = SBC_BE; // To be fixed later ? pa_threaded_mainloop* mainloop = pa_threaded_mainloop_new(); this->mainloop = mainloop; diff --git a/ds4drv/backends/hidraw.py b/ds4drv/backends/hidraw.py index a13798e..5caab6f 100644 --- a/ds4drv/backends/hidraw.py +++ b/ds4drv/backends/hidraw.py @@ -190,7 +190,7 @@ def play_audio(self, headers, data): self.set_volume(60, 60, 0) self._control() - maxtime = 0.1*self.audio_buffer_size/headers.calculate_bit_rate() + maxtime = 0.01*self.audio_buffer_size/headers.calculate_bit_rate() self.write_report(report_id, report, timeout = maxtime) From 1f6338ccb7afd8c92cc084fcd56210ea7afda391 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Fri, 8 Jul 2016 06:35:08 +0100 Subject: [PATCH 09/11] Increased latency. Added boost::circular_buffer for audio buffer for pulse. Added automatic reconnection if pulse is killed. Added stop() to destructor so pulse sink should deconnect even with unexpected process close. --- ds4drv/__main__.py | 2 +- ds4drv/audio/pulseaudio_sbc_stream.cc | 117 +++++++++++++++++--------- ds4drv/audio/pulseaudio_sbc_stream.hh | 6 +- ds4drv/backends/hidraw.py | 21 ++--- 4 files changed, 95 insertions(+), 51 deletions(-) diff --git a/ds4drv/__main__.py b/ds4drv/__main__.py index 8dbe5d5..f3601fb 100644 --- a/ds4drv/__main__.py +++ b/ds4drv/__main__.py @@ -160,7 +160,7 @@ def __call__(self, signum, frame): def main(): sbc_stream = PulseaudioSBCStream( - "ds4drv", "Test\\ ds4drv\\ sink" + "ds4drv", "Test ds4drv sink" ) sbc_stream.run() diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index 78e3465..09714c1 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -26,8 +26,10 @@ void PulseaudioSBCStream::read_pulse_stream( sbc_t* sbc = &(self->audio_loop_sbc); std::size_t sbc_frame_length = sbc_get_frame_length(sbc); - std::size_t sbc_buflen = 10*sbc_frame_length+10; - std::uint8_t sbc_buf[sbc_buflen]; + std::size_t sbc_frame_buflen = 10*sbc_frame_length+10; + std::uint8_t sbc_frame_buf[sbc_frame_buflen]; + + std::size_t sbc_audio_length = sbc_get_codesize(sbc); while(pa_stream_readable_size(s) > 0) { const char* data8 = NULL; @@ -39,31 +41,29 @@ void PulseaudioSBCStream::read_pulse_stream( self->audio_buffer.end(), data8, data8+length ); - for(std::size_t i=0; iaudio_buffer.size() >= sbc_audio_length) { + ssize_t written = 0; - ssize_t written = 0; - std::size_t read = sbc_encode( - sbc, - &(self->audio_buffer[0]), self->audio_buffer.size(), - sbc_buf, sbc_buflen, - &written - ); + std::size_t read = sbc_encode( + sbc, + self->audio_buffer.linearize(), self->audio_buffer.size(), + sbc_frame_buf, sbc_frame_buflen, + &written + ); - if(written > 0) { - // Write frames to supplied file descriptors - for( - FDList::iterator fd_it = self->fds.begin(); - fd_it != self->fds.end(); - fd_it++ - ) { - write(*fd_it, sbc_buf, written); - } + if(written > 0) { - self->audio_buffer.erase( - self->audio_buffer.begin(), self->audio_buffer.begin() + read - ); + // Write frames to supplied file descriptors + for( + FDList::iterator fd_it = self->fds.begin(); + fd_it != self->fds.end(); + fd_it++ + ) { + write(*fd_it, sbc_frame_buf, written); + } + + self->audio_buffer.erase_begin(read); + } } pa_stream_drop(s); @@ -111,9 +111,11 @@ void PulseaudioSBCStream::setup_pulse_stream( buffer_attr.fragsize = (uint32_t) -1; buffer_attr.tlength = (uint32_t) -1; buffer_attr.minreq = (uint32_t) -1; - buffer_attr.fragsize = pa_usec_to_bytes(50, &(i->sample_spec)); + buffer_attr.fragsize = pa_usec_to_bytes(4000, &(i->sample_spec)); - pa_stream_flags_t flags = PA_STREAM_ADJUST_LATENCY; + pa_stream_flags_t flags = static_cast( + PA_STREAM_ADJUST_LATENCY + ); char device_strbuf[1024]; snprintf(device_strbuf, 1024, "%s.monitor", i->name); @@ -133,27 +135,55 @@ void PulseaudioSBCStream::module_setup_cb( pa_operation* op = pa_context_get_sink_info_list( c, setup_pulse_stream, self_v ); - pa_operation_unref(op); + if(op != NULL) pa_operation_unref(op); } void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { + Self* self = static_cast(self_v); + + // Context connected. Setup stream. if(pa_context_get_state(c) == PA_CONTEXT_READY) { printf("[info][PulseaudioSBCStream] Connecting to Pulseaudio\n"); Self* self = static_cast(self_v); + // Build new sink_description string with spaces escaped. + std::string sanitized_description = self->sink_description; + std::size_t last_pos = 0; + std::size_t found = 0; + while( + (found = sanitized_description.find(' ', last_pos)) + != sanitized_description.npos + ) { + sanitized_description.replace(found, 1, "\\ "); + last_pos = found + 2; + } char options_buf[1024]; snprintf( options_buf, 1024, "rate=\"%d\" format=\"%s\" channels=\"%d\"" "sink_name=\"%s\" sink_properties=device.description=\"%s\"", 32000, pa_sample_format_to_string(PA_SAMPLE_S16NE), 2, - self->sink_name.c_str(), self->sink_description.c_str() + self->sink_name.c_str(), sanitized_description.c_str() ); pa_operation* op = pa_context_load_module( c, "module-null-sink", options_buf, module_setup_cb, self_v ); - pa_operation_unref(op); + if(op != NULL) pa_operation_unref(op); + } + + // Context failed. Assume pulse will restart and try reconnecting. + if(pa_context_get_state(c) == PA_CONTEXT_FAILED) { + printf( + "[info][PulseaudioSBCStream] Context failed. Reconnecting...\n" + ); + + unsigned int sleep_time = 1; + + // Try reconnecting every sleep_time seconds. + while(self->setup_context() < 0) { + sleep(sleep_time); + } } } @@ -177,7 +207,7 @@ PulseaudioSBCStream::PulseaudioSBCStream( sink_description(sink_description), sink_module_id(-1), - audio_buffer() + audio_buffer(512*100) { sbc_init(&(this->audio_loop_sbc), 0); @@ -192,6 +222,18 @@ PulseaudioSBCStream::PulseaudioSBCStream( pa_threaded_mainloop* mainloop = pa_threaded_mainloop_new(); this->mainloop = mainloop; + this->setup_context(); +} + +PulseaudioSBCStream::~PulseaudioSBCStream() { + this->stop(); + + pa_context_disconnect(this->context); + + sbc_finish(&(this->audio_loop_sbc)); +} + +int PulseaudioSBCStream::setup_context() { pa_proplist* proplist = pa_proplist_new(); pa_proplist_sets( proplist, PA_PROP_DEVICE_STRING, this->sink_name.c_str() @@ -208,17 +250,14 @@ PulseaudioSBCStream::PulseaudioSBCStream( this->context = c; int err = pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL); - if(err < 0) { + if(err >= 0) { + pa_context_set_state_callback(c, context_state_cb, this); + } + else { printf("[error][PulseaudioSBCStream] Error connecting context\n"); } - pa_context_set_state_callback(c, context_state_cb, this); -} - -PulseaudioSBCStream::~PulseaudioSBCStream() { - pa_context_disconnect(this->context); - - sbc_finish(&(this->audio_loop_sbc)); + return err; } void PulseaudioSBCStream::run() { @@ -235,7 +274,7 @@ void PulseaudioSBCStream::stop() { this->context, this->sink_module_id, unload_module_success, this ); - pa_operation_unref(op); + if(op != NULL) pa_operation_unref(op); } else { unload_module_success(this->context, 0, this); } diff --git a/ds4drv/audio/pulseaudio_sbc_stream.hh b/ds4drv/audio/pulseaudio_sbc_stream.hh index be2c785..21f8011 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.hh +++ b/ds4drv/audio/pulseaudio_sbc_stream.hh @@ -7,6 +7,8 @@ #include #include +#include + #include #include #include @@ -33,7 +35,7 @@ public: uint32_t sink_module_id; sbc_t audio_loop_sbc; - std::vector audio_buffer; + boost::circular_buffer audio_buffer; typedef std::set FDList; FDList fds; @@ -63,6 +65,8 @@ public: ~PulseaudioSBCStream(); + int setup_context(); + void run(); void stop(); diff --git a/ds4drv/backends/hidraw.py b/ds4drv/backends/hidraw.py index 5caab6f..56c00ce 100644 --- a/ds4drv/backends/hidraw.py +++ b/ds4drv/backends/hidraw.py @@ -42,12 +42,14 @@ def pool_init(hidraw_device): # here since Pool hangs otherwise. signal.signal(signal.SIGINT, signal.SIG_IGN) - HidrawWriter.report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) - HidrawWriter.fd = FileIO(HidrawWriter.report_fd, "rb+", closefd=False) + HidrawWriter.report_fd = os.open( + hidraw_device, + os.O_WRONLY | os.O_SYNC + ) @staticmethod def pool_close_fds(): - HidrawWriter.fd.close() + pass @staticmethod def sigalrm_handler(signum, frame): @@ -55,18 +57,17 @@ def sigalrm_handler(signum, frame): @staticmethod def pool_write(data, timeout): - timedout = False - oserror = False - try: if timeout != None: old_sigalrm_handler = signal.getsignal(signal.SIGALRM) signal.signal(signal.SIGALRM, HidrawWriter.sigalrm_handler) signal.setitimer(signal.ITIMER_REAL, timeout) - HidrawWriter.fd.write(data) + os.write(HidrawWriter.report_fd, data) + except TimeoutError: pass + except OSError: pass @@ -88,7 +89,7 @@ class HidrawDS4Device(DS4Device): def __init__(self, name, addr, type, hidraw_device, event_device): try: self.hidraw_writer = HidrawWriter(hidraw_device) - self.report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK) + self.report_fd = os.open(hidraw_device, os.O_RDONLY | os.O_NONBLOCK) self.fd = FileIO(self.report_fd, "rb+", closefd=False) self.input_device = InputDevice(event_device) @@ -133,7 +134,7 @@ def read_feature_report(self, report_id, size): def write_report(self, report_id, data, timeout = None): try: hid = bytearray((report_id,)) - self.hidraw_writer.write(hid + data, timeout) + self.hidraw_writer.write(hid + data, timeout = timeout) except TimeoutError: pass @@ -190,7 +191,7 @@ def play_audio(self, headers, data): self.set_volume(60, 60, 0) self._control() - maxtime = 0.01*self.audio_buffer_size/headers.calculate_bit_rate() + maxtime = 1*self.audio_buffer_size/headers.calculate_bit_rate() self.write_report(report_id, report, timeout = maxtime) From a75177f34d3929290a0c166df34bf5b3c40e8908 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Tue, 19 Jul 2016 11:08:53 +0100 Subject: [PATCH 10/11] Set default audio settings to PS4 settings from example reports at eleccelerator.com. Removed superfluous std::function from pulseaudio_sbc_stream.hh. --- ds4drv/audio/pulseaudio_sbc_stream.cc | 50 +++++++++++++++++++++++---- ds4drv/audio/pulseaudio_sbc_stream.hh | 9 ++--- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index 09714c1..5ee573c 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -77,7 +77,9 @@ void PulseaudioSBCStream::setup_pulse_stream( if(i && eol == 0 && i->owner_module == self->sink_module_id) { - /* Fix sbc encoder format */ + // Fix sbc encoder format + + // Endianness if(i->sample_spec.format == PA_SAMPLE_S16BE) { printf( "[info][PulseaudioSBCStream] " @@ -97,7 +99,39 @@ void PulseaudioSBCStream::setup_pulse_stream( ); } - /* Set up stream */ + // Sample rate + if(i->sample_spec.rate == 16000) { + printf( + "[info][PulseaudioSBCStream] " + "Stream sample rate 16000\n" + ); + self->audio_loop_sbc.frequency = SBC_FREQ_16000; + } else if(i->sample_spec.rate == 32000) { + printf( + "[info][PulseaudioSBCStream] " + "Stream sample rate 32000\n" + ); + self->audio_loop_sbc.frequency = SBC_FREQ_32000; + } + + // Some info + std::size_t sbc_codesize = sbc_get_codesize( + &(self->audio_loop_sbc) + ); + std::size_t sbc_frame_length = sbc_get_frame_length( + &(self->audio_loop_sbc) + ); + printf( + "[info][PulseaudioSBCStream] " + "Stream codesize: %zu\n", sbc_codesize + ); + printf( + "[info][PulseaudioSBCStream] " + "Stream frame_length: %zu\n", sbc_frame_length + ); + + + // Set up stream pa_stream* stream = pa_stream_new( c, self->sink_description.c_str(), &(i->sample_spec), NULL ); @@ -163,7 +197,7 @@ void PulseaudioSBCStream::context_state_cb(pa_context* c, void* self_v) { options_buf, 1024, "rate=\"%d\" format=\"%s\" channels=\"%d\"" "sink_name=\"%s\" sink_properties=device.description=\"%s\"", - 32000, pa_sample_format_to_string(PA_SAMPLE_S16NE), 2, + self->sample_rate, pa_sample_format_to_string(PA_SAMPLE_S16NE), 2, self->sink_name.c_str(), sanitized_description.c_str() ); pa_operation* op = pa_context_load_module( @@ -207,17 +241,19 @@ PulseaudioSBCStream::PulseaudioSBCStream( sink_description(sink_description), sink_module_id(-1), + sample_rate(32000), + audio_buffer(512*100) { sbc_init(&(this->audio_loop_sbc), 0); - this->audio_loop_sbc.frequency = SBC_FREQ_32000; + this->audio_loop_sbc.frequency = SBC_FREQ_32000; // Possibly reset later. this->audio_loop_sbc.blocks = SBC_BLK_16; this->audio_loop_sbc.subbands = SBC_SB_8; - this->audio_loop_sbc.mode = SBC_MODE_STEREO; + this->audio_loop_sbc.mode = SBC_MODE_DUAL_CHANNEL; this->audio_loop_sbc.allocation = SBC_AM_LOUDNESS; - this->audio_loop_sbc.bitpool = 50; - this->audio_loop_sbc.endian = SBC_BE; // To be fixed later ? + this->audio_loop_sbc.bitpool = 25; + this->audio_loop_sbc.endian = SBC_BE; // Possibly reset later. pa_threaded_mainloop* mainloop = pa_threaded_mainloop_new(); this->mainloop = mainloop; diff --git a/ds4drv/audio/pulseaudio_sbc_stream.hh b/ds4drv/audio/pulseaudio_sbc_stream.hh index 21f8011..387751e 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.hh +++ b/ds4drv/audio/pulseaudio_sbc_stream.hh @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -21,12 +20,6 @@ class PulseaudioSBCStream { public: typedef PulseaudioSBCStream Self; - typedef std::function< - void ( - sbc_t* sbc, void* sbc_frame, size_t length - ) - > ReadSBCFrameCallback; - pa_context* context; pa_threaded_mainloop* mainloop; @@ -34,6 +27,8 @@ public: std::string sink_description; uint32_t sink_module_id; + uint32_t sample_rate; + sbc_t audio_loop_sbc; boost::circular_buffer audio_buffer; From 26af45ef397e685725fb69aeb165b59745038367 Mon Sep 17 00:00:00 2001 From: Padraig O Conbhui Date: Wed, 20 Jul 2016 00:17:08 +0100 Subject: [PATCH 11/11] Set -std=c++98 for compiling pulseaudio_sbc_stream extension. Striped out any c++11 only bits. --- ds4drv/audio/pulseaudio_sbc_stream.cc | 3 ++- ds4drv/audio/pulseaudio_sbc_stream.hh | 3 --- setup.py | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ds4drv/audio/pulseaudio_sbc_stream.cc b/ds4drv/audio/pulseaudio_sbc_stream.cc index 5ee573c..10f50cd 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.cc +++ b/ds4drv/audio/pulseaudio_sbc_stream.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -27,7 +28,7 @@ void PulseaudioSBCStream::read_pulse_stream( std::size_t sbc_frame_length = sbc_get_frame_length(sbc); std::size_t sbc_frame_buflen = 10*sbc_frame_length+10; - std::uint8_t sbc_frame_buf[sbc_frame_buflen]; + uint8_t sbc_frame_buf[sbc_frame_buflen]; std::size_t sbc_audio_length = sbc_get_codesize(sbc); diff --git a/ds4drv/audio/pulseaudio_sbc_stream.hh b/ds4drv/audio/pulseaudio_sbc_stream.hh index 387751e..05b2ecb 100644 --- a/ds4drv/audio/pulseaudio_sbc_stream.hh +++ b/ds4drv/audio/pulseaudio_sbc_stream.hh @@ -3,9 +3,6 @@ #include #include -#include -#include - #include #include diff --git a/setup.py b/setup.py index 5340d9e..3a1b3eb 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,8 @@ def swig_sources(self, sources, extension): extra_compile_args=[ subprocess.check_output( "pkg-config --cflags libpulse", shell=True - ).decode('utf-8') + ).decode('utf-8'), + '-std=c++98' ], libraries=["pulse", "sbc"] )