Let's add writers to Core/WindowsMediaAudioFormat!


#1

It occurs to me how desirable it would be if both CoreAudioFormat and WindowsMediaAudioFormat could write (see this thread and this one

We would not only be able to read more, stranger files and behave just like the platform - but we could write all sorts of audio formats including, er, certain compressed proprietary formats that are very popular, without involving Jules’ hosting code that might be infringing.

Now, Jules is as always working flat out - but surely this is something that could be done by the community of readers on this forum?

There are a lot of people here who have a lot of specific Windows Media or Core Audio knowledge, or could find out about how they worked… and we’re talking about simply implementing a single method on a single class, so even though there might be a lot of code and even helper classes behind the scenes, the interface is at least brutally clear and easily testable.

Or… is there some reason this is much harder than it seems?


#2

Nice idea, bring it on!


#3

Its certainly a nice idea. I seem remember however that CoreAudio won’t let you write to an existing stream due to licensing issues with some of the formats, see here. These may be possible to avoid with other format flags however.

The other issue with these multiple codec formats is we need a way of specifying what type of file to write. I suppose the metadataValues argument could be hacked to take the first (or an keyed item) as the format type based on the extensions array.


#4

I’m not sure that your link indicates that CoreAudio can’t create new files of a given type - just that it can’t overwrite existing files…

“There is however a limitation when working with MPEG-4 file types which include; .mp4 (kAudioFileMPEG4Type), .m4a (kAudioFileM4AType), .3g2 (kAudioFile3GP2Type) and .3gp (kAudioFile3GPType).”

“AudioFile does not currently support writing to existing files of these types.”

So if these guys were actually rational, you’d think the phrase “existing” would mean something - but then they go on to say:

“Therefore, if you provide the inWriteFunc procedure it is an indication to AudioFileOpenWithCallbacks that the file is being opened for writing, which in turn results in the kAudioFilePermissionsError being returned.”

which seems to indicate that you get an error if there’s ANY attempt to write. Bah.

But this is only MPEG-4 - surely mp3 files can be written this way, as well as .WAV or .AIFF…?

The other issue with these multiple codec formats is we need a way of specifying what type of file to write. I suppose the metadataValues argument could be hacked to take the first (or an keyed item) as the format type based on the extensions array.

URG.

Big bus crash there. Yes, this is a pretty serious issue with juce::AudioFormat in fact - it’s assuming that the AudioFormat uniquely determines the output file format - but this concept is made a mockery of by such AudioFormats as CoreAudioFormat and WindowsMediaAudio which could write multiple types.

I believe we need to have a new signature to createWriterFor, one which takes a file type:

virtual AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, const String& fileSuffix, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) = 0;

where we use the file suffix as a surrogate for the file type (since there is no pure “audio file type” identifier in Juce…)

The issue is making it backward compatible.

Thoughts?


#5

In fact, that method has gradually gained more and more parameters over the years and should probably be cleaned up by creating a struct that can be passed, containing all the info that it needs, and that could contain details of which format to use.

A file suffix on its own is probably not good enough though, as some suffixes can be applied to more than one format.


#6

Well I wonder if you specify a format that it is allowed to write to (e.g. not kAudioFileMPEG4Type etc.) if the call to AudioFileOpenWithCallbacks will succeed with a write function pointer argument. If so it may just be a case of swapping the source and destination set-up code around and making sure the destination format is set to whatever the user has specified.

The write method should be simple enough to implement as you don’t have to deal with all the seeking and positioning, you just push samples to the stream.

However, this does sound too easy to be true and whenever I’ve coded things with Apple API’s there are always some hidden caveats.


#7

So let’s summarize where we are at so far.

First, AudioFormat::createWriterFor needs a change of signature - let me propose:

[code]
class AudioFormat … {
struct OutputDescription {
unsigned int channels;
int bitsPerSample;
int sampleRate;

String encoderName;       // The name of the output encoder:  mp3, wav, aiff, etc. 
StringPair encoderHints;  // Name/value pairs which might be meaningful to the encoder.

};

// Return a list of the possible values for “encoder name” for this output format.
virtual const StringArray& getEncoderNames() const = 0;

virtual AudioFormatWriter* createWriterFor (OutputStream*, const OutputDescription&) = 0;[/code]

It’s also possible not to set the name, in which case the AudioFormat uses its “default output encoding”. This is how to preserve code compatibility and keep the original signature of createWriterFor() working by delegating to this new virtual method - all the existing AudioFormats will continue to work as is.

So there would be encoderNames like “mp3” with encoderHints like “stereo=joint&bps=128k”.

And there’s a new method, getEncoderNames(), so you can look and see if your desired encoder is supported, or else go on to the next AudioFormat if not.

Thoughts on this data structure?

Second, people on the list need to start building these writers - a little research will find out what’s possible to actually write, dave96 has suggestions above - we can be doing this in parallel to the refactoring of AudioFormat.


#8

OS X (10.4+):
http://developer.apple.com/library/mac/#documentation/MusicAudio/Conceptual/CoreAudioOverview/SupportedAudioConverterFormats/SupportedAudioConverterFormats.html

iOS:
http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html

Windows (Vista+):
link

Windows (8):
link


The methods createWriterFor() of the class CoreAudioFormat and WindowsMediaAudioFormat are not implemented - but it would be super awesome to have them !11!
#9

Ok, I’ve made a start on the CoreAudioFormatWriter and, as expected it is a lot more complicated than expected, damm I hate those Apple C APIs. I have uploaded the progress to a public Git repository incase anyone wants to join in.

So far I’m just trying to get it to write a simple wav file but I can’t get a valid AudioFileID returned from AudioFileInitializeWithCallbacks. It appears that CoreAudio expects to be able to read from the stream it is writing to, this doesn’t really fit with the OutputStream we have access to. I have made some horrible hacks which copy the written data to a MemoryBlock and attempt to read that back when the read callback requests it but still no luck, I always get a kAudioFileInvalidFileError which suggests either its not writing the data correctly or I’m not returning it correctly.

I can’t really afford to spend much more time on this at the moment unless someone can provide a helpful hint. Feel free to fork, request ownership etc. to the repository.


#10

dave96: that was fast!

I have to say that the CoreAudio stuff is pretty daunting. I’m on a laptop in Germany and trying to bring up even all the documentation just floods my work area with information…

It appears that CoreAudio expects to be able to read from the stream it is writing to,

That’s really weird. Wouldn’t that prevent you from e.g. writing to read-only devices - like, say, something wrapping a socket connection, or a CD-ROM?


#11

Yeh, all the Apple stuff is so extensive its really difficult to get know the APIs fully. Also the mixture of C/C++/Obj-C means you have to switch thought modes all the time. I have no doubt they work really well and allow you to do pretty much everything (apparently iMovie on the iPhone is written with entirely public APIs) but man they’re a headache. Some of the docs also suffer from a severe case of copy-and-paste so don’t match up with the function prototypes.

Yeh thats what I thought. It might be that it just expects to be able to re-read the header in which case that might be hackable e.g storing the header in a memory block. It appears the JUCE WavAudioFormat also needs to seek to re-write the header but I guess if it can’t it just leaves the one written at the beginning, not just give up completely.

I have made some progress however, using some horrible dynamic casts and constant InputStream re-creation from the OutputStream sources the header seems to write ok. Now I’m getting kExtAudioFileError_InvalidOperationOrder errors after the first write operation. This could be due to incorrect source/destination format settings though.

I’ll keep working on it and updating the repository.


#12

Whoa, kick-ass!

I so wish I had my full development platform right about now, everything feels like I’m walking in mud… (i.e., so I could take a break from my programming chores and hack on this myself for a bit…)

EDIT: but I figured out something I CAN contribute so I will ask you for access!


#13

All right, I built it!

In order to get it to work, you need of course to go in and change the path to JUCE in the Introjucer. It also seems as if the two PlaybackPage source files are not in the .jucer… but they’re easy to add.

Now to add something on this branch… :smiley:


#14

So I wrote a little bit helper code here… it’s running in my live codebase so it’s fairly solid. (I actually did this right after last week but for some reason haven’t been able to correctly fork the original project… so I put it in my repo.)

RegisterFormats.{h, cpp} has a small function, registerFormats, to replace juce::AudioFormatManager::registerDefaultFormat - because it uses the system provided codecs in preference for reading, and these codecs give better results than Juce, and because if we do ever get juce::Core/WindowsMediaAudioFormat to write you can change the constants in RegisterFormat.h.

AudioFormatOutputDescription.h has the code I proposed earlier in this thread for the new method to AudioMediaFormat, as well as some glue code from the “old” version to the new version. This hasn’t really been tested because a lot of it won’t work without changes to Juce…