繁体   English   中英

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

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

我目前正在制作一个小型 discord 机器人,它可以播放音乐来提高我的技能。 这就是为什么我不使用任何 discord 库的原因。 我希望音乐尽可能流畅,但是当我播放一些音乐时,产生的音乐非常断断续续。 这是我的代码:

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;
                });
        }

有3个可能的原因:

  1. 我延迟发送数据包,因此服务器端缓冲区用完,因此产生的声音在每 2 个数据包之间有一些静音 也许计时器不准确,所以声音不同步。
  2. 编码过程是错误的,会以某种方式导致数据丢失。
  3. 网络不好(我已经测试了一个在 java 上编写的开源机器人,它工作正常,所以我可以假设我的网络足够好)所以我发布了这个问题,希望有人经历过这种情况,告诉我哪里出了问题,我应该怎么做才能纠正它。

我自己发现了问题。 我想在这里为需要的人发布解决方案。 问题是定时器不稳定,所以它通常比它应该睡得更多,所以它会破坏音乐。 我将其更改为准确的睡眠 function,这是我在互联网上某处找到的(我不记得来源,抱歉,如果您知道,请在下面注明)。 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);
        }

谢谢您的支持!

暂无
暂无

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

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