Функция обратного вызова CURLOPT_INTERLEAVEFUNCTION всегда получает nullptr как указатель пользовательских данных - PullRequest
0 голосов
/ 27 мая 2020

Я пытался получить данные из моего потока rtsp через библиотеку CURL (ранее я пробовал использовать libvl c, но тоже потерпел неудачу). По какой-то причине user_data, которую я передаю обратному вызову, который должен получать данные через функцию, всегда имеет значение null. Вот несколько фрагментов кода: Это часть, которая должна запускать и получать пакеты

void CRTSPStream::Run() {
    curl_socket_t socket;
    CURLcode res = curl_easy_getinfo(m_CurrentCURL, CURLINFO_ACTIVESOCKET, &socket);
    Data streamData;
    if(res != CURLE_OK) {
        throw Camera::InputException("RTSP Receive data socket error.", 4003);
    }
    while(m_ShouldRun) {
        m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_INTERLEAVEFUNCTION, &CRTSPStream::WriteCallback);
        if (m_CURLResults != CURLE_OK) throw Camera::InputException("RTSP unable to set INTERLEAVE buffer.", 4004);
        m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_INTERLEAVEDATA, &streamData);
        if (m_CURLResults != CURLE_OK) throw Camera::InputException("RTSP unable to set INTERLEAVEDATA buffer.", 4005);
        m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_RECEIVE);
        if (m_CURLResults != CURLE_OK) throw Camera::InputException("RTSP unable to send RTSP REQUEST.", 4006);
        m_CURLResults = curl_easy_perform(m_CurrentCURL);
        if (m_CURLResults != CURLE_OK) throw Camera::InputException("RTSP unable to receive new data.", 4007);
        //Do something with the data

        m_Logger.Info("New Packet arrived");
    }
}

Это класс, который имеет реализацию функции Run

class CRTSPStream : public Utility::Runnable {
public:
    static const std::string DEFAULT_LOGER_LEVEL;

    CRTSPStream() : m_Logger("RTSP", DEFAULT_LOGER_LEVEL) {
        m_Terminated = true;
        m_ShouldRun = false;
    }
    virtual ~CRTSPStream() { Deinit(); }
    void Init(const std::string& _url, const std::string& _transport, const std::string& _range, const std::string& _outFilename);
    void Deinit();
    void SendDescribe();
    void Setup();
    void Play();
    void SendTeardown();
    void GetMediaControlAttribude();
    void StartReceiving();
    void StopReceiving();
    virtual void Run();
private:
    size_t WriteCallback(void *_ptr, size_t _size, size_t _nmemb, void *_userdata);
    int WaitForRecvPacket(curl_socket_t _sockfd, unsigned long _miliseconds);

    Example::Logger m_Logger;
    Utility::Thread m_RecvThread;
    bool m_Terminated;
    bool m_ShouldRun;

    struct Data {
        size_t m_Size;
        char* m_Data;
    };

    Data* m_Data;
    CURL* m_CurrentCURL;
    CURLcode m_CURLResults;
    curl_version_info_data* m_CurlVersionInfo;
    std::string m_StreamURL;
    std::string m_OutStreamPath;
    std::string m_Transport;
    std::string m_Range;
    char m_Control[256];
    FILE* m_OutStream;
};

Инициализация, точнее, RTSP Describe и другие настройки отправляются через этот метод

void CRTSPStream::Init(const std::string& _url, const std::string &_transport, const std::string &_range, const std::string& _outFilename) {
    std::stringstream ss;
    if((m_CURLResults = curl_global_init(CURL_GLOBAL_ALL)) != CURLE_OK) {
        throw Camera::InputException("RTSP failed curl_global_init", m_CURLResults);
    }
    m_CurlVersionInfo = curl_version_info(CURLVERSION_NOW);
    ss << "RTSP initialization loaded CURL version: " << m_CurlVersionInfo->version;
    m_Logger.Debug(ss.str());
    if((m_CurrentCURL = curl_easy_init()) == nullptr) {
        throw Camera::InputException("RTSP failed curl_easy_init", 5001);
    }
    //This part of the code always succeedes
    curl_easy_setopt(m_CurrentCURL, CURLOPT_VERBOSE, 0L);
    curl_easy_setopt(m_CurrentCURL, CURLOPT_NOPROGRESS, 1L);
    curl_easy_setopt(m_CurrentCURL, CURLOPT_HEADERDATA, stdout);
    curl_easy_setopt(m_CurrentCURL, CURLOPT_URL, _url.c_str());

    if((m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_RTSP_STREAM_URI, _url.c_str())) != CURLE_OK)
        throw Camera::InputException("RTSP failed to set an option to CURLOPT_RTSP_STREAM_URI", m_CURLResults);
    if((m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS)) != CURLE_OK)
        throw Camera::InputException("RTSP failed to set an option to CURLOPT_RTSP_REQUEST", m_CURLResults);
    if((m_CURLResults = curl_easy_perform(m_CurrentCURL)) != CURLE_OK)
        throw Camera::InputException("RTSP failed to execute PERFORM on url: " + _url, m_CURLResults);
    m_OutStreamPath = _outFilename;

    if((m_OutStream = fopen(m_OutStreamPath.c_str(), "wb")) == NULL) {
        m_Logger.Error("RTSP Output failed to open: " + _outFilename);
    } else {
        m_Logger.Info("Writing to " + _outFilename);
    }
    m_Data = new Data();
    m_Transport = _transport;
    m_Range = _range;
    m_StreamURL = _url;
}

И, наконец, это функция обратного вызова:

size_t CRTSPStream::WriteCallback(void* _ptr, size_t _size, size_t _nmemb, void* _userdata) {
    Data* data = (Data*)_userdata;
    data->m_Data = new char[_nmemb*_size];
    memcpy(data->m_Data, _ptr, _size*_nmemb);
    m_Logger.Debug("Memory copied.");
    return m_Data->m_Size = _size * _nmemb;
}

Также Функция Run вызывается сразу после функции Init через StartReceiving, которая запускает новый поток, который вызывает ранее упомянутый Run

1 Ответ

0 голосов
/ 27 мая 2020

Вы пытаетесь использовать нестатический c метод класса для обратного вызова CURLOPT_INTERLEAVEFUNCTION. Это не будет работать. Есть скрытый параметр this, который curl не сможет учесть при вызове обратного вызова. Таким образом, вы ДОЛЖНЫ использовать вместо этого автономную функцию или, по крайней мере, метод класса stati c, чтобы удалить этот параметр.

Для того, что вы пытаетесь для этого измените обратный вызов соответствующим образом, а затем передайте его this в качестве пользовательских данных и сделайте желаемые пользовательские данные членом класса.

class CRTSPStream : public Utility::Runnable {
public:
    ...

    virtual void Run();
private:
    static size_t WriteCallback(void *_ptr, size_t _size, size_t _nmemb, void *_userdata);
    ...

    struct Data {
        size_t m_Size;
        char* m_Data;
    };

    ...
    Data* m_streamData;

    ...
};
void CRTSPStream::Run() {
    ...
    Data streamData;
    m_streamData = &streamData;

    while(m_ShouldRun) {
        m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_INTERLEAVEFUNCTION, &CRTSPStream::WriteCallback);
        ...
        m_CURLResults = curl_easy_setopt(m_CurrentCURL, CURLOPT_INTERLEAVEDATA, this);
        ...
    }
}
size_t CRTSPStream::WriteCallback(void* _ptr, size_t _size, size_t _nmemb, void* _userdata) {
    Data *data = ((CRTSPStream*)_userdata)->m_streamData;
    data->m_Data = new char[_nmemb*_size];
    memcpy(data->m_Data, _ptr, _size*_nmemb);
    m_Logger.Debug("Memory copied.");
    return data->m_Size = _size * _nmemb;
}

Кроме того, обратите внимание, что у вас происходит утечка памяти new[], которую выделяет WriteCallback(), поскольку вы никогда не delete[] ее между вызовами обратного вызова или после выхода curl_easy_perform(). Обратный вызов может вызываться несколько раз, по одному для каждого полученного блока RTSP. Поэтому либо заранее выделите буфер и просто повторно используйте его по мере необходимости, либо, если вам нужно захватить все полученные данные, я бы предложил изменить буфер на vector<char> или std::string вместо char[], таким образом вы можете продолжать добавлять к нему новые данные по мере необходимости, и они будут автоматически освобождены, когда вы закончите использовать его.

...