Play online radio in C++

Hello, this is my first post here!

I've seen other questions similar to mine, but I could not figure out how can I play a sound from an online radio in C++

The link to the stream is https://ssl.hdradios.net:6858/;

If you put this link as src of an audio element of an html page and open it on the browser, you will be able to play the audio. What I need is a something similar, but for C++ instead.
https://beej.us/guide/bgnet/ for how to open network connections and passing data back and forth.

https://www.wireshark.org/ for debugging your message traffic.

 
$ wget --debug --output-file=wget.log --output-document=wget.txt https://ssl.hdradios.net:6858 

This will by itself just fetch a continual stream of data (press ctrl-c after 5 seconds). The log file will contain lots of useful information about what you need to do.

What OS are you using?
What sound library are you using?
Thanks for your reply.

I am using Ubuntu.

I'm (trying) to use SDL and asio, but I don't get any response.

This is a sample where you can reproduce:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#define ASIO_STANDALONE

#include <iostream>

#include <asio.hpp>
#include <asio/ssl.hpp>      

int main()
{
    asio::io_service svc;
    asio::ssl::context ctx(asio::ssl::context::method::sslv23_client);    

    asio::error_code ec;

    asio::ssl::stream<asio::ip::tcp::socket> ssock(svc, ctx);
    asio::ip::tcp::endpoint endpoint(asio::ip::make_address("69.197.183.98", ec), 6858);    

    if(ec)
    {
        std::cout << "Error\n";
        return 0;
    }

    ssock.lowest_layer().connect(endpoint, ec);

    if(ec)
    {
        std::cout << "Error\n";
        return 0;
    }

    ssock.handshake(asio::ssl::stream_base::handshake_type::client);

    std::string request = 
    "GET / HTTP/1.1\n"
    "User-Agent: Wget/1.20.3 (linux-gnu)\n"
    "Accept: */*\n"
    "Accept-Encoding: identity\n"
    "Host: ssl.hdradios.net:6858\n"
    "Connection: Keep-Alive";

    ssock.write_some(asio::buffer(request), ec);

    if(ec)
    {
        std::cout << "Error\n";
        return 0;
    }

    char buffer[1024];

    do {                        
        size_t read_bytes = ssock.read_some(asio::buffer(buffer, 1024), ec);    
    } while (!ec);
} 


link this with thread and openssl. I'm sending the same request as wget.

I'm using asio and sdl because I worked with these before. If you know anything better, feel free to recommend.
that is the nuts and bolts of it. you may want to buffer up several seconds of data so you can keep playing smoothly if the network hiccups. That depends on whether you want cleaner sound or near real time playback though. The library that plays the sounds may do that for you.
You could try writing the HTML document and then opening it using a C++ program with the system() function. I don't use Linux, so I'm not sure how you'd open it exactly...

On my terminal, you would open it simply with the open command.

e.g.

$ open FileServer.html

And it opens it in Safari (the default browser).

I know Linux is similar to Unix, but I'm not sure how similar, exactly.
Hello, jonnin and agent max. Thanks for your reply.

jonnin:

The code I posted is just a sample so salem c could reproduce the problem: the socket receive 0 bytes in response for the request. In the actual code I'm using, I append everything I receive to the end of a std::string, and read from the beginning erasing the read bytes, leaving a buffer which I can use later. I'm caching the data. But thanks for your suggestion and your time.

agent max:

I used this solution previously. Using wxWidgets to create a wxFrame, and then use a wxWebView to open an html page with an audio element. I want to use C++ to play the audio data because I want to have control. Thanks for your suggestion.
Moonslate wrote:
I want to use C++ to play the audio data because I want to have control

Use the HTML document as a sort of interface to the audio site, and control it using a C++ program.

Open it and then use an HTML form to send information to the program, and use the C++ code to "control" the HTML document.

Did I make that clear? I know I'm probably using some wrong terminology.
Hello again, agent max.

In my previous post, I said:


I used this solution previously. Using wxWidgets to create a wxFrame, and then use a wxWebView to open an html page with an audio element.


I actually made the full application, but I can't have full control. ie, auto playing the audio on the page load. This is only possible when replacing the audio element with a video element, but then I lose the ability to create an AudioContext to access the raw data. And also I'm having problems with the libwebkit.

I'm glad you're trying to help, but let's focus on the question
Play online radio in C++
that is what I really need. Thanks for your time.
You're starting from too low level. Don't bother implementing HTTP or dealing directly with SSL. Use an HTTP client library (e.g. libcurl) to do that for you. The library will give you buffers at regular intervals. You hand those to a codec library (e.g. libvorbis or mpeg123) to extract PCM data. Then finally you give that data to an audio device. You should buffer a few seconds worth of audio at this last stage to prevent the device from starving and the audio from breaking up.
Hello, helios.

I forgot to say, but I tried previously with libcurl, but I only get noise on the SDL side. I receive constant update in the libcurl WRITEFUNCTION, so probably it is working on the connection side. You think that the problem is incompatibility with SDL and with libvorbis or mpeg123 I will receive the sound instead of noise? I will write a small sample with libcurl and SDL2 when I come house, so you can see if I'm doing something wrong.

About the low level, I don't bother of having to write everything. I worked a little bit with both, libcurl and ASIO, but I like more to use ASIO, but I don't mind changing libraries.

I cache the sample rate * 30 before starting the playback. I think sample rate * x is the length of x seconds of audio.

Please with some hours for my next post.
If you just sent what the server was sending directly to your sound device, then yeah, obviously that will happen. Internet radio doesn't use uncompressed PCM; that would take far too much bandwidth. All stations use some compressed format and the client has to decompress it first. For a start, figure out what format that station is using and plug the codec into your code. Later if you want you can do something more advanced, like selecting the right codec based just on the data coming from the server.
Hello, helios.

This is what I made with libcurl + SDL2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <string.h>

#include <iostream>

#include <curl/curl.h>

#include <SDL2/SDL.h>

std::string sBuffer;

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
    sBuffer.append((char*)buffer, size * nmemb);
    return size * nmemb;
}

void SDLAudioCallback(void *data, Uint8 *buffer, int length)
{
    memcpy(buffer, sBuffer.data(), length);
    sBuffer.erase(0, length);    
}

int main() {    
    
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

    SDL_Window *window = SDL_CreateWindow("Sample Audio", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);

    SDL_AudioDeviceID device;    

    SDL_AudioSpec wantSpec, haveSpec;

    SDL_zero(wantSpec);    
    wantSpec.freq = 22050;
    wantSpec.format = AUDIO_U8;
    wantSpec.channels = 1;
    wantSpec.samples = 2048;
    wantSpec.callback = SDLAudioCallback;

    device = SDL_OpenAudioDevice(NULL, 0, &wantSpec, &haveSpec, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
    if (device == 0)
    {
        std::cout << "Failed to open audio: " << SDL_GetError() << std::endl;
    }  

    SDL_PauseAudioDevice(device, 0);    

    CURL *curl;
    CURLcode res;

    curl = curl_easy_init();

    if(curl) {

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_URL, " https://ssl.hdradios.net:6858/;");
        res = curl_easy_perform(curl);
    }

    SDL_Event event;
    bool run = true;
    while(run) {
        while(SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_MOUSEBUTTONDOWN:                 
                    break;
                case SDL_QUIT:
                    run = false;
                    break;
                default:
                    break;
            }
        }    
    }

    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}


This is a very minimal sample.

So...

The only options I know is sample rate (22050) and bitrate (64).

These information are in the output of wget. See salem c answer.
The HTTP headers can say anything they want. It doesn't necessarily correlate to what's actually in the stream coming from the server.
Here's what Media Player Classic says the stream actually contains:
Audio: AAC 44100Hz stereo 64kbps


You can't send the compressed audio directly to the sound device, just like how you can't open a ZIP file in a text editor to read the text file contained in the ZIP file. Of course you're going to hear nothing but meaningless noise.

Also, at this point it's mostly irrelevant but it's going to be important eventually: your audio callback function is too expensive. The callback needs to feed buffers to the sound device and thus it's on a soft realtime deadline. It needs to run as fast as possible, because it if misses the deadline the sound device runs out of samples to play and the audio breaks up.
Topic archived. No new replies allowed.