Creating an AudioFormatReader from an InputStream


#1

Hi,

I’m trying to do extensive use of InputStreams everywhere I can to abstract the source type…
It works well with the Image class, but I have a problem with audio files when creating an AudioFormatReader.
There is no way to know the audio format of a stream directly from the AudioFormatManager, so here is what I was doing :

AudioFormatReader* createReaderFor(InputStream*& stream)
{
    AudioFormatManager afm;
    afm.registerBasicFormats();
    for (int i = 0; i < afm.getNumKnownFormats(); ++i)
    {
        AudioFormatReader* afr = afm.getKnownFormat(i)->createReaderFor(stream);
        if (afr)
            return afr; // found
        else
            stream = recreateStream(); // stream has been deleted by afr
    }
    return 0;
}
  1. The stream is deleted if it is not suitable, and that makes the code ugly cause I have to recreate the stream every time (simplified here).
  2. The flac audio format asserts juce_FlacAudioFormat.cpp, line 101 if the stream is not is cup of tea.

So here is what I added this to Juce :

// In class AudioFormat :

/** Returns true if this given stream can be read by this format.

    Subclasses should read some bytes of the stream to decide if it looks good enough.
*/
virtual bool canHandleStream (InputStream* sourceStream) = 0;



// And for each formats :

bool AiffAudioFormat::canHandleStream (InputStream* input)
{
    int64 begin = input->getPosition();
    bool looksCorrect = false;

    if (input->readInt() == chunkName ("FORM"))
    {
        if (input->readInt())
        {
            const int nextType = input->readInt();
            if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC"))
            {
                looksCorrect = true;
            }
        }
    }
    input->setPosition(begin);
    return looksCorrect;
}

bool FlacAudioFormat::canHandleStream (InputStream* input)
{
    int64 begin = input->getPosition();
    bool looksCorrect = false;
    if (input->readInt() == chunkName ("fLaC"))
    {
        looksCorrect = true;
    }
    input->setPosition(begin);
    return looksCorrect;
}

bool OggVorbisAudioFormat::canHandleStream (InputStream* input)
{
    int64 begin = input->getPosition();
    bool looksCorrect = false;
    if (input->readInt() == chunkName ("OggS"))
    {
        looksCorrect = true;
    }
    input->setPosition(begin);
    return looksCorrect;
}

bool WavAudioFormat::canHandleStream (InputStream* input)
{
    int64 begin = input->getPosition();
    bool looksCorrect = false;
    if (input->readInt() == chunkName ("RIFF"))
    {
        if (input->readInt())
        {
            if (input->readInt() == chunkName ("WAVE"))
            {
                looksCorrect = true;
            }
        }
    }
    input->setPosition(begin);
    return looksCorrect;
}

So now it is possible to do this :

static AudioFormatReader* audio_from_stream(InputStream* stream) { static AudioFormatManager afm; if (afm.getNumKnownFormats() == 0) afm.registerBasicFormats(); for (int i = 0; i < afm.getNumKnownFormats(); ++i) if (afm.getKnownFormat(i)->canHandleStream(stream)) return afm.getKnownFormat(i)->createReaderFor(stream); return NULL; }

I couldn’t make extensive tests, but it works for the sample files I have in the four formats.


#2

Yes - good idea, I’d intended to add something along the same lines as the image manager, but hadn’t got round to it yet. I’ll use this as an opportunity to sort it out!


#3

In fact, I think it’s much more straightforward (and almost as fast) to just have a method like this:

[code]AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* in)
{
// you need to actually register some formats before the manager can
// use them to open a file!
jassert (knownFormats.size() > 0);

if (in != 0)
{
    const int64 originalStreamPos = in->getPosition();

    for (int i = 0; i < getNumKnownFormats(); ++i)
    {
        AudioFormatReader* const r = getKnownFormat(i)->createReaderFor (in);

        if (r != 0)
            return r;

        in->setPosition (originalStreamPos);

        // the stream that is passed-in must be capable of being repositioned so 
        // that all the formats can have a go at opening it.
        jassert (in->getPosition() == originalStreamPos);
    }

    delete in;
}

return 0;

}[/code]


#4

Yes,
that will be ok if AudioFormat doesn’t delete the input stream it’s created with,
and if FlacAudioFormat doesn’t assert when stream is not a flac format.


#5

Sorry - I probably should have waited until I’d actually tried compiling that before I posted it! Ok, I’ll sort something out that works properly for the next release.


#6

interesting… could this be done for the imageformats as well ? (so you could plug new formats on the fly without change the codebase and add manually each image format implemented)… could be gr8 :wink:


#7

But there’s already a findImageFormatForStream() method for images (?)


#8

I think he is talking about registering new ImageFileFormat classes.

I actually added some code for doing this :

[code]// in class juce_ImageFileFormat.h :

static void   addSupportedImageFormat(ImageFileFormat* format);

private:
static OwnedArray& getSupportedFormats();
static OwnedArray formats;

// in juce_ImageFileFormat.cpp :
OwnedArray& ImageFileFormat::getSupportedFormats()
{
if (!formats.size())
{
// register the common formats
formats.add(new PNGImageFormat());
formats.add(new JPEGImageFormat());
formats.add(new GIFImageFormat());
}
return formats;
}

OwnedArray ImageFileFormat::formats;

void ImageFileFormat::addSupportedImageFormat(ImageFileFormat* format)
{
getSupportedFormats().add(format);
}

// also modified some bits of this one :
ImageFileFormat* ImageFileFormat::findImageFormatForStream (InputStream& input)
{
OwnedArray& f = getSupportedFormats();

const int64 streamPos = input.getPosition();

for (int i = 0; i < f.size(); ++i)
{
    ImageFileFormat* iff = f[i];
    const bool found = iff->canUnderstand (input);
    input.setPosition (streamPos);

    if (found)
        return iff;
}

return 0;

}

[/code]

I started a freeimage wrapper,

In case you need it, here is a starting point :

[code]class JuceToFreeImageIOWrapper
{
public:
static FreeImageIO* freeimage_io()
{
static FreeImageIO io = { read, write, seek, tell };
return &io;
}

static FREE_IMAGE_FORMAT find_fif(InputStream& stream)
{
FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
fif = FreeImage_GetFileTypeFromHandle(freeimage_io(), (fi_handle)&stream, 16);
if (!FreeImage_FIFSupportsReading(fif))
fif = FIF_UNKNOWN;
return fif;
}

private:
static unsigned DLL_CALLCONV read(void buffer, unsigned size, unsigned count, fi_handle handle)
{
InputStream
is = (InputStream*)handle;
return (unsigned)(is->read(buffer, size * count) / size);
}
static unsigned DLL_CALLCONV write(void buffer, unsigned size, unsigned count, fi_handle handle)
{
return -1;
}
static int DLL_CALLCONV seek(fi_handle handle, long offset, int origin)
{
bool ok = false;
InputStream
is = (InputStream*)handle;
if (origin == SEEK_SET)
ok = is->setPosition(offset);
else if (origin == SEEK_CUR)
ok = is->setPosition(is->getPosition() + offset);
else if (origin == SEEK_END)
ok = is->setPosition(is->getTotalLength());
jassert(ok);
return ok ? (int)is->getPosition() : -1;
}
static long DLL_CALLCONV tell(fi_handle handle)
{
InputStream* is = (InputStream*)handle;
return (long)is->getPosition();
}
};
[/code]

[code]class FreeImageImageFileFormat : public ImageFileFormat
{
public:
const String getFormatName()
{
return “FreeImage”;
}

bool canUnderstand(InputStream& input)
{
return JuceToFreeImageIOWrapper::find_fif(input) != FIF_UNKNOWN;
}

Image* decodeImage (InputStream& input)
{
Image* image = NULL;

/* // You may want to map the file in memory for faster loading
MemoryBlock m;
int64 pos = input->getPosition();
input->readIntoMemoryBlock(m, (int)input->getTotalLength());
input->setPosition(pos);
MemoryInputStream mem_stream(m.getData(), m.getSize(), false);
*/

FREE_IMAGE_FORMAT fif = JuceToFreeImageIOWrapper::find_fif(input);
if (fif != FIF_UNKNOWN)
{
  FIBITMAP* dib = FreeImage_LoadFromHandle(fif, JuceToFreeImageIOWrapper::freeimage_io(), (fi_handle)&input);
  if (dib)
  {
    // FreeImage stores images up side down
    FreeImage_FlipVertical(dib);
    int bpp = FreeImage_GetBPP(dib);
    FREE_IMAGE_COLOR_TYPE  color_type = FreeImage_GetColorType(dib);

    Image::PixelFormat pf = (Image::PixelFormat)-1;
    if (color_type == FIC_RGB && bpp == 24)
      pf = Image::RGB;
    else if (color_type == FIC_RGBALPHA && bpp == 32)
      pf = Image::ARGB;
    else 
    {
      FIBITMAP* converted_dib = FreeImage_ConvertTo32Bits(dib);
      if (converted_dib)
      {
    		FreeImage_Unload(dib);
        dib = converted_dib;
        pf = Image::ARGB;
      }
    }
    if (pf != -1)
    {
      image = new Image((Image::PixelFormat)pf, FreeImage_GetWidth(dib), FreeImage_GetHeight(dib), false);
      int line_stride, pixel_stride;
      uint8* pixels = image->lockPixelDataReadWrite(0, 0, image->getWidth(), image->getHeight(), line_stride, pixel_stride);
      memcpy(pixels, FreeImage_GetBits(dib), FreeImage_GetHeight(dib) * FreeImage_GetPitch(dib));
      image->releasePixelDataReadWrite(pixels);
    }
  }
}

}

bool writeImageToStream (Image& sourceImage, OutputStream& destStream)
{
return false;
}
};
[/code]

You will have to do this for registering :

ImageFileFormat::addSupportedImageFormat(new FreeImageImageFileFormat());

It is not yet finished and well tested, and the alpha is not premultiplied on each pixel as in juce’s format.


#9

yes thomas exactly. i have done something similar, but heh, changing juce codebase is one of the things i avoid to get my hands on… just asking jules to add it to the library (so i don’t have to manually merge files when new releases come in)… :smiley:


#10

yes, it would be nice to have the addSupportedImageFormat() stuff,
but I don’t think freeimage is useful to most of Juce users…


#11

well i don’t use freeimage at all, since i only need to have support for geotiff to display georaster in a gis application test i’m developing… the way to plug an image format on the fly is sure the best approach rather than modify how ImageFileFormat works… alpha blended png for image resources in applications is the only image format i’m using afterall…


#12

I understand,
for my part, I develop a 3D DCC application using opengl and juce::Image is not adapted because there are too few pixelformats and premultiplying alpha is slowing down the loading process too much. So I decided to make my own Image class.
I guess Jules won’t care about this “problem” until he makes an opengl-based GraphicsContext implementation : ) - one day I hope …


#13

eheh what i would love to see implemented, is a runtime interchangeable LowLevelGraphics and ComponentPeer, from software to an accelerated (opengl) one (anyone have tried xgl on gentoo korooraa? whau!)…


#14

It’s on the to-do-list. The only big problem is rendering antialiased polygons in opengl. Oh, and pbuffers having to be a power-of-two in size. And the fact that you can’t overlay transparent windows over an opengl window in winxp. (…and I think there were a few more hassles too, when I last investigated)


#15

That’s very good news,

For antialiasing, I did an OpenglPixelFormat class used in OpenGLComponent context initialisation.
It lets you set up colors, alpha, depth, stencil and accumulation buffers, but also full-scene antialiasing.
It is only working on win32 right now but I will do it on other platforms as soon as I can.
Let me know if you are interrested in this, I would be so glad to participate to this major advance in Juce : )

Also you may want to have a look at Pixel Buffer Objects, it’s a cross-platform and much cleaner replacement for pbuffers, they can use non power of two size, and are now well supported by decent opengl accelerated boards…


#16

That’s very good news,

For antialiasing, I did an OpenglPixelFormat class used in OpenGLComponent context initialisation.
It lets you set up colors, alpha, depth, stencil and accumulation buffers, but also full-scene antialiasing.
It is only working on win32 right now but I will do it on other platforms as soon as I can.
Let me know if you are interrested in this, I would be so glad to participate to this major advance in Juce : )

Also you may want to have a look at Pixel Buffer Objects, it’s a cross-platform and much cleaner replacement for pbuffers, they can use non power of two size, and are now well supported by decent opengl accelerated boards…[/quote]

Yes, would love to take up any offers of help with opengl, as I don’t know too much about it. I’ve got a new release to get out the door at the moment, but will soon come back to this.


#17