简体   繁体   English

如何使线程停止执行(例如:std::this_thread::sleep_for)以获得准确的间隔

[英]How to make a thread stop excution (eg: std::this_thread::sleep_for) for an accturate interval

I am currently making a small discord bot that can play music to improve my skill.我目前正在制作一个小型 discord 机器人,它可以播放音乐来提高我的技能。 That's why i don't use any discord lib.这就是为什么我不使用任何 discord 库的原因。 I want the music as smooth as possible, but when i played some piece of music, the music produced is very choppy.我希望音乐尽可能流畅,但是当我播放一些音乐时,产生的音乐非常断断续续。 here is my code:这是我的代码:

concurrency::task<void> play(std::string id) {
            auto shared_token = std::make_shared<concurrency::cancellation_token*>(&p_token);
            auto shared_running = std::make_shared<bool*>(&running);
            return concurrency::create_task([this, id, shared_token] {
                audio* source = new audio(id); // create a s16le binary stream using FFMPEG
                speak();                       // sending speak packet
                printf("creating opus encoder\n");
                const unsigned short FRAME_MILLIS = 20;
                const unsigned short FRAME_SIZE = 960;
                const unsigned short SAMPLE_RATE = 48000;
                const unsigned short CHANNELS = 2;
                const unsigned int BITRATE = 64000;
                #define MAX_PACKET_SIZE FRAME_SIZE * 5
                int error;
                OpusEncoder* encoder = opus_encoder_create(SAMPLE_RATE, CHANNELS, OPUS_APPLICATION_AUDIO, &error);
                if (error < 0) {
                    throw "failed to create opus encoder: " + std::string(opus_strerror(error));
                }

                error = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(BITRATE));
                if (error < 0) {
                    throw "failed to set bitrate for opus encoder: " + std::string(opus_strerror(error));
                }

                if (sodium_init() == -1) {
                    throw "libsodium initialisation failed";
                }

                int num_opus_bytes;
                unsigned char* pcm_data = new unsigned char[FRAME_SIZE * CHANNELS * 2];
                opus_int16* in_data;
                std::vector<unsigned char> opus_data(MAX_PACKET_SIZE);

                class timer_event {
                    bool is_set = false;

                public:
                    bool get_is_set() { return is_set; };
                    void set() { is_set = true; };
                    void unset() { is_set = false; };
                };

                timer_event* run_timer = new timer_event();
                run_timer->set();

                //this is the send loop
                concurrency::create_task([run_timer, this, shared_token] {
                    while (run_timer->get_is_set()) {
                        speak();
                        int i = 0;
                        while (i < 15) {
                            utils::sleep(1000);
                            if (run_timer->get_is_set() == false) {
                                std::cout << "Stop sending speak packet due to turn off\n";
                                concurrency::cancel_current_task();
                                return;
                            }
                            if ((*shared_token)->is_canceled()) {
                                std::cout << "Stop sending speak packet due to cancel\n";
                                concurrency::cancel_current_task();
                                return;
                            }
                        }
                    }});
                std::deque<std::string>* buffer = new std::deque<std::string>();
                auto timer = concurrency::create_task([run_timer, this, buffer, FRAME_MILLIS, shared_token] {
                    while (run_timer->get_is_set() || buffer->size() > 0) {
                        utils::sleep(5 * FRAME_MILLIS); //std::this_thread::sleep_for
                        int loop = 0;
                        int sent = 0;
                        auto start = boost::chrono::high_resolution_clock::now();
                        while (buffer->size() > 0) {
                            if (udpclient.send(buffer->front()) != 0) { //send frame
                            //udpclient.send ~ winsock sendto
                                std::cout << "Stop sendding voice data due to udp error\n";
                                return;
                            }
                            buffer->pop_front();
                            if ((*shared_token)->is_canceled()) {
                                std::cout << "Stop sending voice data due to cancel\n";
                                concurrency::cancel_current_task();
                            }
                            sent++; //count sent frame

                            //calculate next time point we should (in theory) send next frame and store in *delay*
                            long long next_time = (long long)(sent+1) * (long long)(FRAME_MILLIS) * 1000 ;
                            auto now = boost::chrono::high_resolution_clock::now();
                            long long mcs_elapsed = (boost::chrono::duration_cast<boost::chrono::microseconds>(now - start)).count(); // elapsed time from start loop
                            long long delay = std::max((long long)0, (next_time - mcs_elapsed));
                            //wait for next time point
                            boost::asio::deadline_timer timer(context_io);
                            timer.expires_from_now(boost::posix_time::microseconds(delay));
                            timer.wait();
                        }     
                    }
                    });
                unsigned short _sequence = 0;
                unsigned int _timestamp = 0;
                while (1) {
                    if (buffer->size() >= 50) {
                        utils::sleep(FRAME_MILLIS);
                    }

                    if (source->read((char*)pcm_data, FRAME_SIZE * CHANNELS * 2) != true) 
                        break;
                    if ((*shared_token)->is_canceled()) {
                        std::cout << "Stop encoding due to cancel\n";
                        break;
                    }

                    in_data = (opus_int16*)pcm_data;
                    num_opus_bytes = opus_encode(encoder, in_data, FRAME_SIZE, opus_data.data(), MAX_PACKET_SIZE);
                    if (num_opus_bytes <= 0) {
                        throw "failed to encode frame: " + std::string(opus_strerror(num_opus_bytes));
                    }

                    opus_data.resize(num_opus_bytes);

                    std::vector<unsigned char> packet(12 + opus_data.size() + crypto_secretbox_MACBYTES);

                    packet[0] = 0x80;   //Type
                    packet[1] = 0x78;   //Version

                    packet[2] = _sequence >> 8; //Sequence
                    packet[3] = (unsigned char)_sequence;

                    packet[4] = _timestamp >> 24;   //Timestamp
                    packet[5] = _timestamp >> 16;
                    packet[6] = _timestamp >> 8;
                    packet[7] = _timestamp;

                    packet[8] = (unsigned char)(ssrc >> 24);    //SSRC
                    packet[9] = (unsigned char)(ssrc >> 16);
                    packet[10] = (unsigned char)(ssrc >> 8);
                    packet[11] = (unsigned char)ssrc;

                    _sequence++;
                    _timestamp += SAMPLE_RATE / 1000 * FRAME_MILLIS; //48000Hz / 1000 * 20(ms)

                    unsigned char nonce[crypto_secretbox_NONCEBYTES];
                    memset(nonce, 0, crypto_secretbox_NONCEBYTES);

                    for (int i = 0; i < 12; i++) {
                        nonce[i] = packet[i];
                    }

                    crypto_secretbox_easy(packet.data() + 12, opus_data.data(), opus_data.size(), nonce, key.data());

                    packet.resize(12 + opus_data.size() + crypto_secretbox_MACBYTES);

                    std::string msg;
                    msg.resize(packet.size(), '\0');

                    for (unsigned int i = 0; i < packet.size(); i++) {
                        msg[i] = packet[i];
                    }
 
                    buffer->push_back(msg);
                }
                run_timer->unset();
                timer.wait();   
                unspeak();
                delete run_timer;
                delete buffer;

                opus_encoder_destroy(encoder);

                delete[] pcm_data;
                });
        }

There are 3 possible causes:有3个可能的原因:

  1. I send packet late so server-end buffer run out, so the sound produced has some silence between each each 2 packets.我延迟发送数据包,因此服务器端缓冲区用完,因此产生的声音在每 2 个数据包之间有一些静音 Maybe the timer is not accurate so the sound is out of sync.也许计时器不准确,所以声音不同步。
  2. The encode process is wrong which causes lost data somehow.编码过程是错误的,会以某种方式导致数据丢失。
  3. Bad network (i have tested an open source bot written on java, it worked so i can assume that my network is good enough) So i post this question, hope someone has experienced this situation show me what wrong and what should i do to correct it.网络不好(我已经测试了一个在 java 上编写的开源机器人,它工作正常,所以我可以假设我的网络足够好)所以我发布了这个问题,希望有人经历过这种情况,告诉我哪里出了问题,我应该怎么做才能纠正它。

I figured out the problem myself.我自己发现了问题。 I want to post solution here for someone who need.我想在这里为需要的人发布解决方案。 The problem is the timer is unstable so it's usually sleep more than it should, so it makes the music broken.问题是定时器不稳定,所以它通常比它应该睡得更多,所以它会破坏音乐。 I changed it to an accurate sleep function which i found somewhere on the internet(i don't remember the source, sorry for that, if you know it please credit it bellow).我将其更改为准确的睡眠 function,这是我在互联网上某处找到的(我不记得来源,抱歉,如果您知道,请在下面注明)。 Function source code: Function源代码:

#include <math.h>
#include <chrono>
#include <window.h>
static void timerSleep(double seconds) {
            using namespace std::chrono;

            static HANDLE timer = CreateWaitableTimer(NULL, FALSE, NULL);
            static double estimate = 5e-3;
            static double mean = 5e-3;
            static double m2 = 0;
            static int64_t count = 1;

            while (seconds - estimate > 1e-7) {
                double toWait = seconds - estimate;
                LARGE_INTEGER due;
                due.QuadPart = -int64_t(toWait * 1e7);
                auto start = high_resolution_clock::now();
                SetWaitableTimerEx(timer, &due, 0, NULL, NULL, NULL, 0);
                WaitForSingleObject(timer, INFINITE);
                auto end = high_resolution_clock::now();

                double observed = (end - start).count() / 1e9;
                seconds -= observed;

                ++count;
                double error = observed - toWait;
                double delta = error - mean;
                mean += delta / count;
                m2 += delta * (error - mean);
                double stddev = sqrt(m2 / (count - 1));
                estimate = mean + stddev;
            }

            // spin lock
            auto start = high_resolution_clock::now();
            while ((high_resolution_clock::now() - start).count() / 1e9 < seconds);
        }

Thank you for your support!谢谢您的支持!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM