Trying to simulate constant byte rate. Confusion with time.sleep results

时光总嘲笑我的痴心妄想 提交于 2020-01-10 05:45:08

问题


Context

I'm using windows 7 on my computer(the player) and linux(debian) on my college computer(the streamer), which I control using ssh.
I was trying to simulate a constant byte rate of a microphone from reading a wave file, as if someone was talking. The problem was that the byte rate was below the target.

Choosing a 32KB/s rate, and 0.020 seconds of capture time.
I implemented the simulated microphone using time.sleep to produce give each chunk of data each 0.020 seconds. But the rate obtained was around 27KB/s, not 32KB/s

The problem

I decided to test how much precise time.sleep was on the linux machine, using ideas by reading this question.

I did 2 kind of tests. 1) busy sleep 2) normal sleep

In average, from the samples I got, it shows that the linux machine sleep resolution is 4ms. While on windows its less/equal to 1ms.

Questions

  1. What can possibly be limiting the sleep resolution on the linux machine?
  2. (On linux) Why does busy sleeping has the same resolution as time.sleep?
  3. How could I successfully simulate a microphone from reading a wave file?

Code

import time

def busy_sleep(t):
    s=time.time()
    while time.time() - s < t:
        pass
    e=time.time()
    return e-s

def normal_sleep(t):
    s=time.time()
    time.sleep(t)
    e=time.time()
    return e-s

def test(fun):
    f = lambda x: sum(fun(x) for d in range(10))/10
    print("0.100:{}".format(f(0.100)))
    print("0.050:{}".format(f(0.050)))
    print("0.025:{}".format(f(0.025)))
    print("0.010:{}".format(f(0.010)))
    print("0.009:{}".format(f(0.010)))
    print("0.008:{}".format(f(0.008)))
    print("0.007:{}".format(f(0.007)))
    print("0.006:{}".format(f(0.006)))
    print("0.005:{}".format(f(0.005)))
    print("0.004:{}".format(f(0.004)))
    print("0.003:{}".format(f(0.003)))
    print("0.002:{}".format(f(0.002)))
    print("0.001:{}".format(f(0.001)))

if __name__=="__main__":
    print("Testing busy_sleep:")
    test(busy_sleep)
    print("Testing normal_sleep:")
    test(normal_sleep)

Results

"""
Debian
Testing busy_sleep:
0.100:0.10223722934722901
0.050:0.051996989250183104
0.025:0.027996940612792967
0.020:0.02207831859588623
0.010:0.011997451782226562
0.009:0.011997222900390625
0.008:0.009998440742492676
0.007:0.007997279167175292
0.006:0.0079974365234375
0.005:0.007997465133666993
0.004:0.005918483734130859
0.003:0.003997836112976074
0.002:0.0039977550506591795
0.001:0.003997611999511719
Testing normal_sleep:
0.100:0.1020797061920166
0.050:0.051999988555908205
0.025:0.028000001907348634
0.020:0.02192000865936279
0.010:0.011999979019165039
0.009:0.012000055313110351
0.008:0.010639991760253906
0.007:0.008000001907348633
0.006:0.00799997329711914
0.005:0.008000059127807617
0.004:0.006159958839416504
0.003:0.004000000953674317
0.002:0.00399998664855957
0.001:0.004000091552734375

$ uname -a
Linux 3.2.0-4-amd64 #1 SMP Debian 3.2.57-3+deb7u2 x86_64 GNU/Linux
"""

"""
Windows 7

Testing busy_sleep:
0.100:0.10000572204589844
0.050:0.05000288486480713
0.025:0.0250014066696167
0.010:0.010500597953796388
0.009:0.010500597953796388
0.008:0.008000493049621582
0.007:0.00740041732788086
0.006:0.006400299072265625
0.005:0.005400300025939942
0.004:0.004700303077697754
0.003:0.003200197219848633
0.002:0.002700185775756836
0.001:0.0016000032424926759
Testing normal_sleep:
0.100:0.10000579357147217
0.050:0.0500028133392334
0.025:0.02500150203704834
0.010:0.01000049114227295
0.009:0.0100006103515625
0.008:0.008000493049621582
0.007:0.007000398635864257
0.006:0.006000304222106934
0.005:0.00500030517578125
0.004:0.0040001869201660155
0.003:0.0030002117156982424
0.002:0.0020000934600830078
0.001:0.0010000944137573243
"""

Real code

import os import wave import sys import io import time

FORMAT = 8 #get_format_from_width(2)
NCHANNELS = 1
FRAMERATE = 16000 # samples per second
SAMPWIDTH = 2 # bytes in a sample
BYTE_RATE = FRAMERATE*SAMPWIDTH
CHUNK_DURATION = 0.020
CHUNK_BYTES = int(CHUNK_DURATION*BYTE_RATE)

class StreamSimulator:
    def __init__(self):
        wf = wave.open("Kalimba.wav","rb")
        buf = io.BytesIO()
        buf.write(wf.readframes(wf.getnframes()))
        wf.close()
        buf.seek(0)
        self.buf = buf
        self.step = time.time()

    def delay(self):
        #delay
        delta = time.time() - self.step 
        self.step=time.time()
        delay = CHUNK_DURATION - delta      
        if delay > 0.001:
            time.sleep(delay)


    def read(self):
        buf = self.buf  
        data = buf.read(CHUNK_BYTES)
        if len(data) == 0:
            buf.seek(0)
            data = buf.read(CHUNK_BYTES)

        self.delay()
        return data

    def close(self):
        self.buf.close()

class DynamicPainter:
    def __init__(self):
        self.l=0

    def paint(self,obj):        
        str1=str(obj)
        l1=len(str1)
        bs="\b"*self.l
        clean=" "*self.l
        total = bs+clean+bs+str1
        sys.stdout.write(total)
        sys.stdout.flush()
        self.l=l1

if __name__=="__main__":
    painter = DynamicPainter()
    stream = StreamSimulator()
    produced = 0
    how_many = 0
    painted = time.time()
    while True:
        while time.time()-painted < 1:
            d = stream.read()
            produced += len(d)
            how_many += 1
        producing_speed = int(produced/(time.time()-painted))       
        painter.paint("Producing speed: {} how many: {}".format(producing_speed,how_many))
        produced=0
        how_many=0
        painted = time.time()

Edit

Changed "Real Code" , added measure of time including sleeping time.
But now I have the DOUBLE Byte Rate: Producing speed: 63996 how many: 100
This got me so MUCH confused. I have tried with different byterates and it ends up being the double everytime.

Conclusion

Thanks to @J.F.Sebastian and his code, I came to learned that:

  • Its better to use use a deadline as a time reference than create to a new reference each loop
  • Using a deadline "amortizes" the imprecision of time.sleep, oscilating a bit around the desired bitrate but resulting in an correct(and much more constant) average.
  • You only need to you use time.time() once, that means less calculations imprecisions.

As a result, I get a constant 32000 B/s some times oscilating to 31999 and very rarely to 31745
Now I can hear the music without any lag or jitter!

Final Code

def read(self):
    buf = self.buf  
    data = buf.read(CHUNK_BYTES)
    if len(data) == 0:
        buf.seek(0)
        data = buf.read(CHUNK_BYTES)

    self.deadline += CHUNK_DURATION 
    delay = self.deadline - time.time()
    if delay > 0:
        time.sleep(delay)
    return data

回答1:


Conclusion

Thanks to @J.F.Sebastian and his code, I came to learned that:

  • Its better to use use a deadline as a time reference than create to a new reference each loop
  • Using a deadline "amortizes" the imprecision of time.sleep, oscilating a bit around the desired bitrate but resulting in an correct(and much more constant) average.
  • You only need to you use time.time() once, that means less calculations imprecisions.

As a result, I get a constant 32000 B/s some times oscilating to 31999 and very rarely to 31745
Now I can hear the music without any lag or jitter!

I tried using @J.F.Sebastian implentation using only the % operator to sleep the remainder, but the KB/s oscilate strangelly, so I decided to keep the deadline implementation, which suffers imprecision by keeping adding a float value. Nevertheless, the overall result is enough for my needs.
Thank you everyone.

Final Code

def read(self):
    self.deadline += 0.020  
    delay = self.deadline - time.perf_counter()
    if delay > 0:
        time.sleep(delay)
    return self._read()



回答2:


As you can read in the question you linked, there are no guarantees for sleep and it can vary widely depending on the OS. But 4 ms resolution seems sufficient if you want to send data every 20 ms. There should be no need to improve your sleep() accuracy.

On Debian, at around 0.02 seconds input, your computer sleeps around 12 / 10 of the time you ask it to. 10 / 12 * 32 is 26.6, so it makes sense if you only receive 27 KB/s.

Instead of sleeping 0.02 seconds, use an adaptive sleeping time. Measure how long the last iteration took (sleeping + sending the data), and shorten your sleeping time to make the whole iteration take 0.02 seconds.



来源:https://stackoverflow.com/questions/26595419/trying-to-simulate-constant-byte-rate-confusion-with-time-sleep-results

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!