1

我在网上搜索过,但找不到一个很好的例子来说明我想要完成的事情。使用 c++,我正在处理 Kodi(一个开源媒体中心应用程序)的可视化。这种可视化将数据作为 http 客户端发送到 Philips Hue 桥接器(http 服务器),以更改灯光的颜色和其他属性以匹配音乐。我正在使用 cURL 来处理 http 请求。

使用一个线程会导致 cURL 花费时间完成其工作并从 Philips Hue 桥(http 服务器)接收响应的问题。这会阻止屏幕上的可视化,有时还会阻止音频输出。您可以在此处查看延迟:可视化的 YouTube 视频

Kodi 中附加组件的结构方式意味着没有“主要”功能。所以对于多线程,我想不出一个好的结构,因为网络上的大多数示例在 main 中创建一个线程,然后在 main 中加入它。目前,我正在尝试这样的事情:

  • start 函数创建一个工作线程
  • 工作线程等待直到堆栈被 audiodata 函数填充
  • 一旦堆栈被填充,工作线程读取顶部元素,发送 cURL 请求,并弹出元素
  • 销毁函数应该加入线程吗?

我尝试了多种创建线程的技术,但是在 Windows 中进行调试时,它们都会导致如下错误:

First-chance exception at 0x0856335A (visualization.wavforhue.dll) in Kodi.exe: 0xC0000005: Access violation reading location 0xFEEEFEF6.
First-chance exception at 0x76CDC52F in Kodi.exe: Microsoft C++ exception: access_violation at memory location 0x003BEC5C.
First-chance exception at 0x76CDC52F in Kodi.exe: Microsoft C++ exception: access_violation at memory location 0x003BEC5C.

或者

Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted.std::this_thread::sleep_for(std::chrono::seconds(2));.

或在函数完成abort();后由 Kodi 调用。start


在下面的代码中启用 curl 调用后,我收到一个不同的错误。它发生在第二次尝试播放歌曲时。第一次尝试成功。

错误是The ordinal 4445 could not be located in the dynamic link library LIBEAY32.dll.此库与 SSL 相关联,我的代码未使用该库。但是,我一定会以某种方式影响 Kodi 程序中的其他 curl 实例。

有趣的部分是没有 curl 调用,代码似乎可以正常运行。我想如果我能成功解决LIBEAY32.dll问题,这可以标记为已解决。

这是完整的代码(底部有 GPL 通知以提高可读性):

//--------------------------------------------------------------------------------------
#include <xbmc_vis_dll.h>
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <curl/curl.h>
#include <string>
//------------------------------------------------------------------


//------------------------------------------------------------------
#include <atomic>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
//----------------------------------------------------------------------


// Thread initialization -------------------------------------------------
std::mutex gMutex;
std::condition_variable gThreadConditionVariable;
std::atomic<bool> gRunThread;
bool gReady;
std::thread gWorkerThread;
std::queue<int> gQueue;
// End thread initialization ---------------------------------------------


void workerThread()
{
  bool isEmpty;
  std::string json;
  // This thread comes alive when Create(), Start(), or AudioData() 
  // is invoked by the main program.
  // It runs until Destroy() or Stop() is invoked.
  while (gRunThread)
  {
    //check that an item is on the stack

    {
      std::lock_guard<std::mutex> lock(gMutex);
      isEmpty = gQueue.empty();
    }

    if (isEmpty)
    {
      //Wait until AudioData() sends data.
      std::unique_lock<std::mutex> lock(gMutex);
      gThreadConditionVariable.wait(lock, []{return gReady; });
    }
    else
    {
      std::lock_guard<std::mutex> lock(gMutex);
      int value = gQueue.front();
      gQueue.pop();
    }
    if (!isEmpty)
    {
      /*
      CURL *curl = curl_easy_init();
      CURLcode res;
      json = "{\"hue\":" + std::to_string(rand() % 60000) + "}";
      // Now specify we want to PUT data, but not using a file, so it has to be a CUSTOMREQUEST
      curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
      curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
      //curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noop_cb);
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str());
      // Set the URL that is about to receive our POST. 
      curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.10.6/api/KodiVisWave/lights/3/state");
      // Perform the request, res will get the return code
      res = curl_easy_perform(curl);
      // always cleanup curl
      curl_easy_cleanup(curl);
      */
    }
  }
}




//-- Create -------------------------------------------------------------------
// Called on load. Addon should fully initalize or return error status
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_Create(void* hdl, void* props)
{
  if (!props)
    return ADDON_STATUS_UNKNOWN;

  gRunThread = true;
  gReady = false;

  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }

  // Must initialize libcurl before any threads are started.
  //curl_global_init(CURL_GLOBAL_ALL);

  return ADDON_STATUS_OK;
}

//-- Start --------------------------------------------------------------------
// Called when a new soundtrack is played
//-----------------------------------------------------------------------------
extern "C" void Start(int iChannels, int iSamplesPerSec, int iBitsPerSample, const char* szSongName)
{
  gRunThread = true;
  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }
}

//-- Audiodata ----------------------------------------------------------------
// Called by XBMC to pass new audio data to the vis
//-----------------------------------------------------------------------------
extern "C" void AudioData(const float* pAudioData, int iAudioDataLength, float *pFreqData, int iFreqDataLength)
{
  // Processing audio data
  if (rand() % 7 == 3)
  {
    std::lock_guard<std::mutex> lock(gMutex);
    gQueue.push(1);
  }

  gRunThread = true;
  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }

  // Send the curl calls to the worker thread
  {
    std::lock_guard<std::mutex> lock(gMutex);
    gReady = true;
  }
  gThreadConditionVariable.notify_one();

}

//-- Stop ---------------------------------------------------------------------
// This dll must stop all runtime activities
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Stop()
{
  gRunThread = false;
  while (gWorkerThread.joinable())
  {
    gWorkerThread.join();
  }
}

//-- Detroy -------------------------------------------------------------------
// Do everything before unload of this add-on
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Destroy()
{
  gRunThread = false;
  while (gWorkerThread.joinable())
  {
    gWorkerThread.join();
  }
}

//-- Render -------------------------------------------------------------------
// Called once per frame. Do all rendering here.
//-----------------------------------------------------------------------------
extern "C" void Render()
{

}

//-- GetInfo ------------------------------------------------------------------
// Tell XBMC our requirements
//-----------------------------------------------------------------------------
extern "C" void GetInfo(VIS_INFO* pInfo)
{
  pInfo->bWantsFreq = false;
  pInfo->iSyncDelay = 0;
}

//-- OnAction -----------------------------------------------------------------
// Handle XBMC actions such as next preset, lock preset, album art changed etc
//-----------------------------------------------------------------------------
extern "C" bool OnAction(long flags, const void *param)
{
  bool ret = false;
  return ret;
}

//-- GetPresets ---------------------------------------------------------------
// Return a list of presets to XBMC for display
//-----------------------------------------------------------------------------
extern "C" unsigned int GetPresets(char ***presets)
{
  return 0;
}

//-- GetPreset ----------------------------------------------------------------
// Return the index of the current playing preset
//-----------------------------------------------------------------------------
extern "C" unsigned GetPreset()
{
  return 0;
}

//-- IsLocked -----------------------------------------------------------------
// Returns true if this add-on use settings
//-----------------------------------------------------------------------------
extern "C" bool IsLocked()
{
  return false;
}

//-- GetSubModules ------------------------------------------------------------
// Return any sub modules supported by this vis
//-----------------------------------------------------------------------------
extern "C" unsigned int GetSubModules(char ***names)
{
  return 0; // this vis supports 0 sub modules
}

//-- HasSettings --------------------------------------------------------------
// Returns true if this add-on use settings
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" bool ADDON_HasSettings()
{
  return false;
}

//-- GetStatus ---------------------------------------------------------------
// Returns the current Status of this visualization
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_GetStatus()
{
  return ADDON_STATUS_OK;
}

//-- GetSettings --------------------------------------------------------------
// Return the settings for XBMC to display
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" unsigned int ADDON_GetSettings(ADDON_StructSetting ***sSet)
{
  return 0;
}

//-- FreeSettings --------------------------------------------------------------
// Free the settings struct passed from XBMC
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------

extern "C" void ADDON_FreeSettings()
{
}

//-- SetSetting ---------------------------------------------------------------
// Set a specific Setting value (called from XBMC)
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_SetSetting(const char *strSetting, const void* value)
{
  return ADDON_STATUS_OK;
}

//-- Announce -----------------------------------------------------------------
// Receive announcements from XBMC
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Announce(const char *flag, const char *sender, const char *message, const void *data)
{
}

/*
 *      Copyright (C) 2008-2016 Team Kodi
 *      http://kodi.tv
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

有一个更好的方法吗?如果不是,我应该在销毁函数中做什么来减轻访问冲突?


这是一个非常简单的源代码版本的链接,没有复杂的声音数据处理、图形渲染和 curl 调用。它仍然失败Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted.at std::this_thread::sleep_for(std::chrono::seconds(2));


编辑:上面链接的代码现在应该可以工作了。我仍然在 Windows 7 和 8.1 上遇到 DirectX 11 和 cURL 的单独问题,但 OpenELEC、Ubuntu 和 Android 对这种结构感到满意。完整实现在视频中链接或切换到 GitHub 中的 master 分支。

4

1 回答 1

0

//在这里做什么来确保线程结束?

看来这里正确的做法是join线程。显然,代码当前的结构方式您无权访问该thread对象。

void workerThread()
{
    CurlData workerCurlData;
    while (true)
    {
        ....
    }
}

在这里,我认为您可能希望将硬编码替换为true一个变量(例如,可能是 type std::atomic<bool>),该变量可以初始化true并设置false为何时需要关闭线程。

负责关闭线程的函数将分配false然后调用join以等待线程完成其执行。


笔记:

  • 似乎在函数的循环开始处有一个不受mutex(即)保护的调用if (putStack.empty())whileworkerThread
  • 您可能还想研究std::lock_guard如何安全地处理锁定和解锁std::mutex
  • 您可能希望用适当的条件变量替换硬编码的睡眠(即,std::condition_variable让线程进入睡眠状态,直到它需要再次开始工作(即,而不是使用轮询方法)

示例代码

#include <atomic>
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>

std::mutex gMutex;
std::queue<int> gQueue;
std::atomic<bool> gRunThread(true);
std::thread gWorkerThread;

void workerThread();

void driver();

void start();

void addData(int i);

void end();

int main()
{
    std::thread driverThread(&driver);
    driverThread.join();

    return 0;
}

void driver()
{
    std::cout << "Starting ...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    start();

    std::this_thread::sleep_for(std::chrono::seconds(1));
    for (auto i = 0; i < 5; ++i)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        addData(i);
    }

    std::cout << "Ending ...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    end();
}

void workerThread()
{
    while (gRunThread)
    {
        bool isEmpty;

        {
            std::lock_guard<std::mutex> lock(gMutex);
            isEmpty = gQueue.empty();
        }

        if (isEmpty)
        {
            std::cout << "Waiting for the queue to fill ...\n";
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
        else
        {
            std::lock_guard<std::mutex> lock(gMutex);

            int value = gQueue.front();
            gQueue.pop();

            std::cout << "Dequeued: " << value << "\n";
        }
    }
}

void start()
{
    gWorkerThread = std::thread(&workerThread);
}

void addData(int i)
{
    {
        std::lock_guard<std::mutex> lock(gMutex);
        gQueue.push(i);
    }

    std::cout << "Queued: " << i << "\n";
}

void end()
{
    gRunThread = false;
    gWorkerThread.join();
}

示例代码输出

Starting ...
Waiting for the queue to fill ...
Waiting for the queue to fill ...
Queued: 0
Queued: 1
Dequeued: 0
Dequeued: 1
Waiting for the queue to fill ...
Queued: 2
Queued: 3
Dequeued: 2
Dequeued: 3
Waiting for the queue to fill ...
Queued: 4
Ending ...

现场示例


编辑

从评论中我现在了解到您的代码位于动态加载和卸载的库中。卸载库将导致破坏全局线程对象。因此,需要处理将卸载库的任何操作,以确保线程正确关闭。

于 2016-02-17T01:55:59.097 回答