Question

I am developing an extremely simple audio player. It uses Gtk and portaudio/libsndfile. I've created a simple test interface with few buttons like Browse, Play etc. My player picks filename correctly, and after pressing Play it starts playing. But everything waits for playback to be finished. Nothing is active and I wonder how to stop it before it finishes by itself.

My code for Gtk is:

#include <stdio.h>
#include <gtk\gtk.h>
static GtkWidget *window;
const char *filename;

static void play_file (GtkButton *button, gpointer data)
{
    sndFile_play(filename);
}
static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    return FALSE;
}

static void destroy( GtkWidget *widget,
                     gpointer   data )
{
    gtk_main_quit ();
}

int main( int   argc,
          char *argv[] )
{

    GtkWidget *button;
    GtkWidget *box1;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "MyPlayer");

    g_signal_connect (window, "delete-event",
              G_CALLBACK (delete_event), NULL);

    g_signal_connect (window, "destroy",
              G_CALLBACK (destroy), NULL);

    gtk_container_set_border_width (GTK_CONTAINER (window), 20);

    box1 = gtk_hbox_new (FALSE, 0);
    gtk_container_add (GTK_CONTAINER (window), box1);

    button = gtk_button_new_with_label ("Browse...");
    g_signal_connect (button, "clicked",
        G_CALLBACK (browse_clicked), NULL); //not given here
    gtk_box_pack_start (GTK_BOX(box1), button, TRUE, TRUE, 0);
    gtk_widget_show (button);

    button = gtk_button_new_with_label ("Play");
    g_signal_connect (button, "clicked",
        G_CALLBACK (play_file), NULL);
    gtk_box_pack_start (GTK_BOX(box1), button, TRUE, TRUE, 0);
    gtk_widget_show (button);

    gtk_widget_show (box1);
    gtk_widget_show (window);

    gtk_main ();

    return 0;
}

If it is necessary I give my code for playing sound:

#include <stdio.h>
#include <stdlib.h>
#include <portaudio.h>
#include <sndfile.h>

#define FRAMES_PER_BUFFER (1024)
#define PA_SAMPLE_TYPE  paInt16
typedef short SAMPLE;
#define BUFFER_LEN  128
#define MAX_CHANNELS 2

int sndFile_play (const char *infilename){
    PaStreamParameters outputParameters;
    PaStream *stream;
    PaError err;
    static short data[BUFFER_LEN];
    SNDFILE      *infile;
    SF_INFO     sfinfo;
    sf_count_t  readcount;
    int channels;
    int srate;

    err = Pa_Initialize();

    if ( !( infile = sf_open( infilename, SFM_READ, &sfinfo ) ) ) {
        printf ("Not able to open input file %s.\n", infilename);
        puts (sf_strerror (NULL));
        return  1;
    }

    if ( sfinfo.channels > MAX_CHANNELS ){
        printf ("Not able to process more than %d channels\n", MAX_CHANNELS);
        return  1;
    }
    /* FILE INFO */

    channels = sfinfo.channels; // убрать channels
    printf("number of channels %d\n", sfinfo.channels);

    srate = sfinfo.samplerate; //убрать 
    printf("sample rate %d\n", sfinfo.samplerate);

    outputParameters.device = Pa_GetDefaultOutputDevice();
    outputParameters.channelCount = sfinfo.channels;
    outputParameters.sampleFormat =  PA_SAMPLE_TYPE;
    outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
    outputParameters.hostApiSpecificStreamInfo = NULL;

    printf("Begin playback.....\n"); fflush(stdout);
    err = Pa_OpenStream(
        &stream,
        NULL,
        &outputParameters,
        sfinfo.samplerate,
        FRAMES_PER_BUFFER,
        paClipOff,
        NULL,
        NULL );

    if( stream ) {

        err = Pa_StartStream( stream );
        printf("Waiting for playback to finish....\n"); fflush(stdout);

        while ( ( readcount = sf_read_short( infile, data,  BUFFER_LEN*sfinfo.channels ) ) ){
            err = Pa_WriteStream( stream, data, BUFFER_LEN );
        }

        err = Pa_CloseStream( stream );
        printf("Done.\n"); fflush(stdout);

    }

    sf_close( infile );

    Pa_Terminate();
    return 0;
}
Was it helpful?

Solution

Your code

while ((readcount = sf_read_short( infile, data,  BUFFER_LEN*sfinfo.channels ) ) ){
    err = Pa_WriteStream( stream, data, BUFFER_LEN );
}

is stopping the Gtk main loop from progressing. For GUI applications you can't have long running while loops in callbacks or the GUI will freeze. There are a few solutions

1) Drive the run loop inside the while loop using something like

...

while (gtk_events_pending ()) {
    gtk_main_iteration ();
}

...

This means that each time the audio loop is executed, all the gtk events that have built up will be processed.

2) Drive the audio from an idle callback. This allows the GUI to work like normal, and then when it's not busy, your audio callback will be called and you can play the next buffer.

3) Drive the audio loop in a separate thread. This will allow the GUI to work like normal on the main thread, and the audio will just play. This carries all the standard dangers of threaded programming

4) Portaudio has an asynchronous callback driven model. Use that and your audio callback will be called when Portaudio needs more data. This is also quite hard because you shouldn't do file access, or memory allocations in that callback. Some more details are here: Portaudio callback documentation.

To be honest, audio programming on this base level is complex and I'd recommend that you use a framework like (for example) GStreamer which has all these complexities worked out already.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top