|
- #!/usr/bin/env python
-
- from ctypes import c_int, pointer, POINTER, sizeof, c_char, c_uint8, c_int16, cast
- from pyogg import opus
- from pyogg.opus import opus_int16, opus_int16_p, c_uchar
-
- import array
- import functools
- import operator
- import pyaudio
- import queue
- import sys
- import time
- import wave
-
- statusvals = [ 'paInputUnderflow', 'paInputOverflow', 'paOutputUnderflow', 'paOutputOverflow', 'paPrimingOutput' ]
- statuses = { getattr(pyaudio, y): y for y in statusvals }
-
- def printstatus(x):
- r = []
- while x:
- b = x & -x # get a single bit
- r.append(statuses[b])
- x &= ~b # clear the bit we added
-
- return ', '.join(r)
-
- rate = 48000
- framelength = .0025
- maxbw = 128*1024
- sampperframe = int(framelength * rate) # 5ms latency
-
- pa = pyaudio.PyAudio()
-
- for i in range(pa.get_device_count()):
- print(i, repr(pa.get_device_info_by_index(i)))
-
- #sys.exit(0)
- class _OpusBase(object):
- @staticmethod
- def _check_err(err):
- if err != opus.OPUS_OK:
- raise RuntimeError('failed', err)
-
- class OpusDecoder(_OpusBase):
- def __init__(self, rate, nchan):
- err = c_int()
- self._nchan = nchan
- self._bytespersamp = nchan * sizeof(opus.opus_int16)
- #print('foo:', repr((self._bytespersamp, nchan, sizeof(opus.opus_int16))))
- self._pcmbuf = (opus_int16 * (nchan * int(rate * framelength)))()
- self._dec = opus.opus_decoder_create(rate, nchan, pointer(err))
- self._check_err(err.value)
-
- def decode(self, frm):
- #print(repr(frm))
- r = opus.opus_decode(self._dec, cast(frm, POINTER(c_uchar)), len(frm), self._pcmbuf, len(self._pcmbuf), 0)
- if r < 0:
- self._check_err(r)
-
- return array.array('h', self._pcmbuf[:self._nchan * r]).tobytes()
-
- def __del__(self):
- opus.opus_decoder_destroy(self._dec)
- self._dec = None
-
- class OpusEncoder(_OpusBase):
- def __init__(self, rate, nchan, app):
- err = c_int()
- self._nchan = nchan
- self._bytespersamp = nchan * sizeof(opus.opus_int16)
- #print('bar:', repr((self._bytespersamp, nchan, sizeof(opus.opus_int16))))
- self._frbuf = (c_char * (self._bytespersamp * int(maxbw * framelength)))()
- self._enc = opus.opus_encoder_create(rate, nchan, app, pointer(err))
- self._check_err(err.value)
-
- def get_max_bw(self):
- val = opus.opus_int32()
- r = opus.opus_encoder_ctl(self._enc, opus.OPUS_GET_MAX_BANDWIDTH, pointer(val))
- self._check_err(r)
- return val.value
-
- def set_max_bw(self, maxbw):
- r = opus.opus_encoder_ctl(self._enc, opus.OPUS_SET_MAX_BANDWIDTH, opus.opus_int32(maxbw))
- self._check_err(r)
-
- def get_inband_fec(self):
- val = opus.opus_int32()
- r = opus.opus_encoder_ctl(self._enc, opus.OPUS_GET_INBAND_FEC_REQUEST, pointer(val))
- self._check_err(r)
- return val.value
-
- def set_inband_fec(self, maxbw):
- r = opus.opus_encoder_ctl(self._enc, opus.OPUS_SET_INBAND_FEC_REQUEST, opus.opus_int32(maxbw))
- self._check_err(r)
-
- def get_pkt_loss(self):
- val = opus.opus_int32()
- r = opus.opus_encoder_ctl(self._enc, opus.OPUS_GET_PACKET_LOSS_PERC_REQUEST, pointer(val))
- self._check_err(r)
- return val.value
-
- def set_pkt_loss(self, percent):
- r = opus.opus_encoder_ctl(self._enc, opus.OPUS_SET_PACKET_LOSS_PERC_REQUEST, opus.opus_int32(percent))
- self._check_err(r)
-
- def encode(self, pcm):
- fs = len(pcm) // self._bytespersamp
- #print(repr(pcm), fs, repr(opus_int16_p), repr(self._nchan))
- #print('baz:', fs, repr((len(pcm), self._bytespersamp)))
- r = opus.opus_encode(self._enc, cast(pcm, opus_int16_p), fs, cast(self._frbuf, POINTER(c_uchar)), len(self._frbuf))
- #r = opus.opus_encode(self._enc, pcm, fs, self._frbuf, len(self._frbuf))
- if r < 0:
- self._check_err(r)
-
- #print(repr(self._frbuf), dir(self._frbuf))
- return self._frbuf.raw[:r]
-
- def __del__(self):
- opus.opus_encoder_destroy(self._enc)
- self._enc = None
-
- enc = OpusEncoder(rate, 1, opus.OPUS_APPLICATION_RESTRICTED_LOWDELAY)
- dec = OpusDecoder(rate, 1)
-
- enc.set_inband_fec(1)
- enc.set_pkt_loss(5)
- print('pl:', repr(enc.get_pkt_loss()))
- print('if:', repr(enc.get_inband_fec()))
-
- #sys.exit(0)
-
- inbuffer = queue.Queue()
- outbufferinfo = []
- outbuffer = []
- adcdiff = []
- dacdiff = []
- adcdacdiff = []
-
- times = []
-
- def excprinter(func):
- @functools.wraps(func)
- def func_wrapper(*args, **kwargs):
- global times
- try:
- s = time.time()
- r = func(*args, **kwargs)
- times.append((s, time.time()))
- except:
- import traceback
- traceback.print_exc()
- raise
-
- return r
-
- return func_wrapper
-
- #wf = wave.open('foo.wav', 'wb')
- #wf.setnchannels(1)
- #wf.setsampwidth(2)
- #wf.setframerate(rate)
-
- cnt = 0
- starttime = None
- combstatus = 0
-
- @excprinter
- def incallback(in_data, frm_cnt, timeinfo, status):
- inbuffer.put((in_data, frm_cnt, timeinfo, status))
-
- if time.time() - s < 5:
- return ('', pyaudio.paContinue)
-
- inbuffer.put(None) # signal finished
- return ('', pyaudio.paComplete)
-
- def outcallback(in_data, frm_cnt, timeinfo, status):
- outbufferinfo.append((in_data, frm_cnt, timeinfo, status))
-
- if outbuffer:
- buf = outbuffer.pop(0)
- else:
- buf = '\x00\x00' * sampperframe
-
- if buf is None:
- return (dbuf, pyaudio.paComplete)
-
- return (buf, pyaudio.paContinue)
-
- instream = pa.open(rate=rate, channels=1, format=pyaudio.paInt16, input_device_index=0, input=True, frames_per_buffer=sampperframe, stream_callback=incallback)
- outstream = pa.open(rate=rate, channels=1, format=pyaudio.paInt16, output_device_index=3, output=True, frames_per_buffer=sampperframe, stream_callback=outcallback)
- print('il:', instream.get_input_latency())
- print('ol:', outstream.get_output_latency())
-
- s = time.time()
-
- print('starting')
- instream.start_stream()
- outstream.start_stream()
- while True:
- #print('sleep')
- d = inbuffer.get()
- if d is None:
- break
- in_data, frm_cnt, timeinfo, status = d
-
- combstatus |= status
- if starttime is None:
- starttime = timeinfo
- lasttime = timeinfo
-
- #print('pcb:', repr((type(in_data), len(in_data), frm_cnt, timeinfo, status)))
- adcdiff.append(timeinfo['current_time'] - timeinfo['input_buffer_adc_time'])
- dacdiff.append(timeinfo['output_buffer_dac_time'] - timeinfo['current_time'])
- adcdacdiff.append(timeinfo['output_buffer_dac_time'] - timeinfo['input_buffer_adc_time'])
- cnt += len(in_data)
- #buf = enc.encode(in_data)
- buf = in_data
- #print('r:', len(buf), repr(buf), repr(type(buf)))
- outbuffer.append(buf)
-
- instream.stop_stream()
- outstream.stop_stream()
- instream.close()
- outstream.close()
- print('done')
-
- #print(repr(adcdiff))
- #print(repr(dacdiff))
- print(max(adcdacdiff), min(adcdacdiff))
- print(cnt)
- print(starttime)
- print(outbufferinfo[0][2])
- print(lasttime)
- print(outbufferinfo[-1][2])
- print(lasttime['current_time'] - starttime['current_time'])
- print('in status:', printstatus(combstatus))
- print('out status:', printstatus(functools.reduce(operator.__or__, (x[3] for x in outbufferinfo), 0)))
- ltimes = [ (e - s) * 1000 for s, e in times ]
- print(min(ltimes), max(ltimes))
- pa.terminate()
|