How to stream audio to IBM Watson Speech-to-text using libCURL in C or C++?

Viewed 74

My ultimate goal is to stream live audio to IBM Watson STT but right now I'm trying to stream an audio file to IBM Watson STT using libCURL in C++.

I was able to post a short audio file to IBM Watson STT using this simple code (for anyone interested):

code-listing-1:

#include <cstdio>
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdlib>

int main(void)
{

    // reading the audio file to memory
    std::ifstream file("audio-file.ogg", std::ios::binary | std::ios::ate);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<char> log(size);
    if(!file.read(log.data(), size)) {
        exit(-1);
    }

    // copying it to char buffer
    char * data = new char[log.size()];
    for(unsigned int i = 0; i < log.size(); i++) data[i] = log[i];

    
    // doing the thing
    
    struct curl_slist *hs = NULL;

    CURL *curl = curl_easy_init();
    CURLcode res;

    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.{location}.speech-to-text.watson.cloud.ibm.com/instances/{instance_id}/v1/recognize");

        hs = curl_slist_append(hs, "Content-Type: audio/ogg");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);

        /* get verbose debug output please */
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);


        /* allow whatever auth the server speaks */
        curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
        curl_easy_setopt(curl, CURLOPT_USERPWD, "apikey:{MY_API_KEY}");

        /* size of the POST data */
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, log.size());

        /* pass in a pointer to the data - libcurl will not copy */
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);

        res = curl_easy_perform(curl);
        if(res != CURLE_OK)
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));


        /* always cleanup */
        curl_easy_cleanup(curl);
    }

    return 0;
}

this works perfectly for short audio file (I tried 7 seconds audio files) but really long audio don't seem to work (I tried 4+ minutes audio).

Now I'm trying to stream the audio instead. I'm thinking I can stream longer audio files that way (even though that's not really what I want to do).

According to the doc I should use the Transfer-Encoding: chunked header and depending of the file I should mention mention a specific Content-Type or Content-Type: application/octet-stream

So I came up with the following code based on libcurl Transfer Encoding example:

code-listing-2:

#include <cstdio>
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdlib>


struct WriteThis {
    const char *readptr;
    size_t sizeleft;
};

static size_t read_callback(char *dest, size_t size, size_t nmemb, void *userp)
{
    struct WriteThis *wt = (struct WriteThis *)userp;
    size_t buffer_size = size*nmemb;

    if(wt->sizeleft) {
        /* copy as much as possible from the source to the destination */
        size_t copy_this_much = wt->sizeleft;
        if(copy_this_much > buffer_size)
        copy_this_much = buffer_size;
        memcpy(dest, wt->readptr, copy_this_much);

        wt->readptr += copy_this_much;
        wt->sizeleft -= copy_this_much;
        return copy_this_much; /* we copied this many bytes */
    }

    return 0; /* no more data left to deliver */
}

int main(void)
{
    CURL *curl;
    CURLcode res;

    struct WriteThis wt;




    // reading the audio file to memory
    std::ifstream file("audio-file.ogg", std::ios::binary | std::ios::ate);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<char> log(size);
    if(!file.read(log.data(), size)) {
        exit(-1);
    }

    // copying it to char buffer
    char * data = new char[log.size()];
    for(unsigned int i = 0; i < log.size(); i++) data[i] = log[i];

    
    // doing the thing

    wt.readptr = data;
    wt.sizeleft = log.size();

    /* In windows, this will init the winsock stuff */
    res = curl_global_init(CURL_GLOBAL_DEFAULT);
    /* Check for errors */
    if(res != CURLE_OK) {
        fprintf(stderr, "curl_global_init() failed: %s\n",
        curl_easy_strerror(res));
        return 1;
    }

    /* get a curl handle */
    curl = curl_easy_init();
    if(curl) {
        /* First set the URL that is about to receive our POST. */
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.{location}.speech-to-text.watson.cloud.ibm.com/instances/{instance_id}/v1/recognize");

        /* Now specify we want to POST data */
        curl_easy_setopt(curl, CURLOPT_POST, 1L);

        /* we want to use our own read function */
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

        /* pointer to pass to our read function */
        curl_easy_setopt(curl, CURLOPT_READDATA, &wt);

        /* get verbose debug output please */
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);


        
        /* allow whatever auth the server speaks */
        curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
        curl_easy_setopt(curl, CURLOPT_USERPWD, "apikey:{MY_API_KEY}");


        struct curl_slist *chunk = NULL;
        chunk = curl_slist_append(chunk, "Content-Type: application/octet-stream");
        chunk = curl_slist_append(chunk, "Transfer-Encoding: chunked");
        res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);


        /* Perform the request, res will get the return code */
        res = curl_easy_perform(curl);
        /* Check for errors */
        if(res != CURLE_OK)
        fprintf(stderr, "curl_easy_perform() failed: %s\n",
        curl_easy_strerror(res));

        /* always cleanup */
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

This code, unfortunately, doesn't work. I get a 401 Error and the libcurl response/log is as follow (only writing the response part) :

  • old SSL session ID is stale, removing < HTTP/2 401

< strict-transport-security: max-age=31536000; includeSubDomains;

< www-authenticate: Basic realm="IBM Watson Gateway(Log-in)"

< content-length: 164

< content-type: application/json

< x-dp-watson-tran-id: 47f2d5e9-a720-4114-885c-8c8804317c1b

< x-request-id: 47f2d5e9-a720-4114-885c-8c8804317c1b

< x-global-transaction-id: 47f2d5e9-a720-4114-885c-8c8804317c1b

< server: watson-gateway

< x-edgeconnect-midmile-rtt: 79

< x-edgeconnect-origin-mex-latency: 230

< date: Tue, 10 Aug 2021 11:51:46 GMT

  • necessary data rewind wasn't possible
  • stopped the pause stream!
  • Connection #0 to host api.eu-gb.speech-to-text.watson.cloud.ibm.com left intact curl_easy_perform() failed: Send failed since rewinding of the data stream failed

What should I write to stream a buffered audio file to IBM Watson STT (and therefore will be able to stream live audio next).

Thanks in advance!

0 Answers
Related