如何使线程停止执行例如:std::this_thread::sleep_for一个准确的间隔

问题描述

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

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,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,crypto_secretBox_NONCEBYTES);

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

                    crypto_secretBox_easy(packet.data() + 12,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 编写的开源机器人,它工作正常,所以我可以假设我的网络足够好) 所以我发布了这个问题,希望有人经历过这种情况,告诉我哪里出了问题,我应该怎么做来纠正它。

解决方法

我自己解决了这个问题。我想在这里为需要的人发布解决方案。 问题是定时器不稳定,所以它通常睡得比它应该的多,所以它会使音乐中断。 我把它改成了一个准确的睡眠功能,我在互联网上的某个地方找到了它(我不记得来源,抱歉,如果你知道,请在下面注明)。 函数源码:

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

感谢您的支持!