views:

137

answers:

2

Hello,

I'm working on playing audio from an audio stream using VC++ with the QtMultimedia library. Since I'm not too experienced with Qt's libraries I started by reading in a .wav file and writing it to a buffer:

ifstream wavFile;
char* file = "error_ex.wav";
wavFile.open( file, ios::binary );

After that, I used ifstream's .read() function and write all the data into a buffer. After the buffer is written it's sent off to the audio writer that prepares it for Qt:

QByteArray fData;

for( int i = 0; i < (int)data.size(); ++i )
{
    fData.push_back(data.at(i));
}

m_pBuffer->open(QIODevice::ReadWrite);
m_pBuffer->write( fData );

m_pBuffer->close();

(m_pBuffer is of type QBuffer)

Once the QBuffer is ready I attempt to play the buffer:

QIODevice* ioDevice = m_pAudioOut->start();
ioDevice->write( m_pBuffer->buffer() );

(m_pAudioOut is of type QAudioOutput)

This results in a small pop from the speakers and then it stops playing. Any ideas why?

Running Visual Studios 2008 on Windows XP SP2 using Qt library 4.6.3.

A: 

Are you sure you use the right (high-level) API? It would be weird if you had to handle data streams and buffering manually. Also, QIODevice::write() doesn't necessarily write the whole buffer but might stop after n bytes, just like POSIX write() (that's why one always should check the return value).

I didn't look into QtMultimedia yet, but using the more mature Phonon, video and audio output worked just fine for me in the past. It works like this:

  1. Create a Phonon::AudioOutput object
  2. Create a Phonon::MediaObject object
  3. Phonon::createPath( mediaObject, audioObject )
  4. mediaObject->setCurrentSource( Phonon::MediaSource( path ) );
  5. mediaObject->play();

There are also examples in Qt.

Frank
Frank, I've looked into Phonon first since it claimed to support playing streams. However, whenever I set the source to a stream url, i.e. rtp://@123.123.12.1:8080, it doesn't play the stream. Phonon is also reliant on what system you're working on as it doesn't implement its own backend (according to Qt documentation). For example, on Windows Phonon uses backend support from DirectShow and on Linux, GStreamer.
Tony
@Tony: both Phonon and QtMultimedia require a backend, which uses the appropriate native API(s) to implement the public Qt API. At present, the `QAudio*` backend is statically compiled into QtMultimedia.dll whereas Phonon backends are built as separate DLLs and loaded via the Qt plugin mechanism. But from a design perspective, both Phonon and QtMultimedia consist of a generic implementation, a backend interface and multiple, platform-specific backend implementations.
Gareth Stockwell
A: 

As Frank pointed out, if your requirement is simply to play audio data from a file, a higher-level API would do the job, and would simplify your application code. Phonon would be one option; alternatively, the QtMobility project provides the QMediaPlayer API for high-level use cases.

Given that the question is specifically about using QIODevice however, and that you mentioned that reading from a WAV file was just your intitial approach, I'll assume that you actually need a streaming API, i.e. one which allows the client to control the buffering, rather than handing over this control to a higher-level abstraction such as Phonon.

QAudioOutput can be used in two different modes, depending on which overload of start() is called:

  • "Pull mode": void QAudioOutput::start(QIODevice *)

    In this mode, QAudioOutput will pull data from the supplied QIODevice without further intervention from the client. It is a good choice if the QIODevice being used is one which is provided by Qt (e.g. QFile, QAbstractSocket etc).

  • "Push mode": QIODevice* QAudioOutput::start()

    In this mode, the QAudioOutput client must push mode to the audio device by calling QIODevice::write(). This will need to be done in a loop, something like:

    qint64 dataRemaining = ... // assign correct value here
    while (dataRemaining) {
        qint64 bytesWritten = audioOutput->write(buffer, dataRemaining);
        dataRemaining -= bytesWritten;
        buffer += bytesWritten;
        // Then wait for a short time
    }
    

    How the wait is implemented will depend on the context of your application - if audio is being written from a dedicated thread, it could simply sleep(). Alternatively, if audio is being written from the main thread, you will probably want the write to be triggered by a QTimer.

    Since you don't mention anything about using a loop around the write() calls in your app, it looks like what is happening is that you write a short segment of data (which plays as a pop), then don't write any more.

You can see code using both modes in the examples/multimedia/audiooutput app which is delivered with Qt.

Gareth Stockwell
Ah! Thanks this is a much better source of information than just the Qt documentation.I'm "cloning" the example code that is relevant to my needs. I have run into two issues though. When I attempt to use pull-mode, I get the same result as before along with some QObject warning about how QTimers need to be started in a thread. Then, when I use push mode, the read call is returning -1 (error). Which leads me to believe something with my buffer is wrong.I will continue to work on this. Thanks for the help.
Tony
So I'm looking more into the pull method. My "play" method simply opens the QBuffer and then the QAudioOutput starts it (this follows the example along with Qt documentation). It sounds like it plays the first note and then stops. This method shouldn't require me to manually make sure it plays all the packets. Ideas?
Tony
My hunch was right. Running it in a command line application caused the program to terminate and kill the audio output nearly instantly. I added my code to a GUI application and it worked! Thanks for the help!
Tony
@Tony: as you found, you need an event loop in order to play the entire clip. You don't necessarily need a GUI app however - `QCoreApplication::exec()` will work just as well as `QApplication::exec()` for the purposes of creating such a loop.
Gareth Stockwell