This project has moved. For the latest updates, please go here.

How to concatenate two Wave files in memory to play

Nov 28, 2014 at 4:51 PM
Hi all,

I want to know how to concatenate two Wave files in memory to play. I don't want to save a result wave file. I just only one to reproduce the two files as one.

Is it possible?

Thank you very much in advance,
Fran
Coordinator
Nov 28, 2014 at 5:51 PM
I'd make my own IWaveProvider (or WaveStream if you need to reposition within the concatenated file) that took the two WaveFileReaders as inputs. In the Read method, read out of the first one, and if you get less than count, start reading out of the second one. Both files must have the same waveformat.
Marked as answer by jonnymela on 12/1/2014 at 5:01 AM
Dec 1, 2014 at 12:01 PM
Thank you very much for your help markheath. I have concatenate more than one file in memory using a new provider. :)
Feb 27, 2015 at 6:47 PM
Hello all

please read below if you care about the big picture. The specific thing we're looking for is how to build such an IWaveProvider that can concatenate two (or more) WAV files in memory. Here's our take on it:

https://gist.github.com/BrainCrumbz/091de69fbbec6ad5fcd6

but we're facing an unhandled COMException when we try to encode starting from such a provider.

Could you please help us understanding what's wrong, or point us to a working provider that can join WAV files?

Thanks all for your time!


BIG PICTURE:
we're working with NAudio in order to read some WAV files, combine (aka join or concatenate) them together in memory and then saving the result as WMA file.

We had hard times trying to even convert a single file from WAV to WMA using NAudio.WMA Nuget, that is NAudio.WindowsMediaFormat namespace. When using WmaWriter.Write we faced the "scary" Object contains non-primitive or non-blittable data exception from deep down in GCHandle.Alloc in WmaWriter c'tor and we decided to pull back for the moment.

So we switched to NAudio.MediaFoundation, as in several places it has been suggested as a simpler way to achieve WMA encoding. That has proved to be true: we could easily convert a single WAV file to a WMA file quite soon.

Now the trouble we have is how to combine two (or more) WAV files into an IWaveProvider, which can then be fed into MediaFoundationEncoder.EncodeToWma.
Coordinator
Mar 1, 2015 at 7:00 AM
what is the WaveFormat of the WAV files? The error you are getting seems to be that the encoder cannot find a suitable WMA encoding to convert to.
Mar 2, 2015 at 10:46 AM
Edited Mar 2, 2015 at 10:48 AM
Thanks for your time (and for this great library)

The WaveFormat of WAV files is {16 bit PCM: 44kHz 1 channels}. Although, for the records, I'd like to tell that:
  1. We were able to concatenate both WAV files into a new WAV file, checking that the sources had the same format
  2. We were able to convert a single WAV file into a new WMA file, using the same MediaType that we're trying to use now
  3. We were able to join WAV files into a new WMA file, using a MixingSampleProvider, but this resulted somehow in "cutting" the end of both files before joining them (that's why we're trying with a custom provider as suggested in this thread)
Finally, after posting our question, we went on with tests. We tried both using an "implicit" encoder with the single line:
MediaFoundationEncoder.EncodeToWma(waveProvider, outputFile, 16000);
as well as with an "explicit" encoder:
                MediaType wmaMediaType = MediaFoundationEncoder.SelectMediaType(
                    AudioSubtypes.MFAudioFormat_WMAudioV8,
                    new WaveFormat(16000, 1),
                    16000);

                using (MediaFoundationEncoder wmaEncoder = new MediaFoundationEncoder(wmaMediaType))
                {
                    wmaEncoder.Encode(outputFile, waveProvider);
                }
The strange result is that - whether explicit or implicit, whether with mixing provider or custom one - if we start the test application and try with a join right away, we get that exception. Instead, if we first try with converting a single file, and then go with the join, no exception is thrown.

Both operations - joining two files and saving a single WAV file - share much of the same code.
Mar 2, 2015 at 11:06 AM
Edited Mar 12, 2015 at 12:06 PM
Here's a link to the sample solution:

https://drive.google.com/open?id=0B1icYE0oSducVzY5TGFyRThOT2M&authuser=0

The code we're talking about is the one in MediaFoundation directory. Code-behind (no MVVM in this test, sorry) almost directly invokes that, and does basically nothing regarding audio manipulation.

In order to test, just run the app and click on the various buttons.

EDIT

The link to sample solution on Google Drive is not valid anymore. If anyone is interested, we uploaded a Proof-of-Concept solution on GitHub with a working technique as well.
Mar 2, 2015 at 1:33 PM
Oops, sorry about that, I thought I had shared the link with enough permission! Now I accepted your request from Google Drive.
Mar 2, 2015 at 1:51 PM
Edited Mar 2, 2015 at 1:58 PM
Double checking the source code, one difference we can see is the following:
  1. Code for converting a single WAV file into WMA accesses the source WAV through MediaFoundationReader, which is then fed as IWaveProvider into encoding operation
  2. Code for joining WAV files into a single WMA accesses the source WAV files through WaveFileReader, which are then used to build the IWaveProvider and in turn this is fed into encoding operation
Could this be the root cause? We're going to test this right now.

EDIT:

That was it. We substitued every occurence of WavFileReader with MediaFoundationReader when working with MediaFoundation, and now our custom WaveProvider works great.

Thanks again for your attention

Regards
Coordinator
Mar 2, 2015 at 3:39 PM
hi, have just had a look at your project. It's because you're not calling MediaFoundationApi.Startup() before using Media Foundation APIs. I think there are some places where I do this automatically for you, but obviously its not in there everywhere at the moment.
Mar 3, 2015 at 8:10 AM
Ok, I see. Thanks for the suggestion: we'll make sure we run that at least once.
Kind regards

The BrainCrumbz team
Mar 4, 2015 at 4:59 PM
Edited Mar 4, 2015 at 5:04 PM
Apart from all this (preliminary problems?) we still have issues with the core of the thing: the IWaveProvider that joins WAV files in memory. Initially it seemed to work ok, but now we're trying with 5 or 6 short clips (a start tone, some spoken letters or numbers from an alphanumeric code, an ending tone) and we can hear that the final clips are somehow cut, or not played to the end anyway.

As from the initial gist linked before, the core is represented by the following code blocks:
// Code invoking the provider and saving WMA
private static int JoinToWmaFile(string outputPathName, MediaType wmaMediaType, IEnumerable<MediaFoundationReader> waveReaders)
{
    // ...
    IWaveProvider joiningWaveProvider  = new JoiningWaveProvider(waveReaders);

    using (MediaFoundationEncoder wmaEncoder = new MediaFoundationEncoder(wmaMediaType))
    {
        wmaEncoder.Encode(outputPathName, joiningWaveProvider);
    }

}
// The actual provider
public JoiningWaveProvider(IEnumerable<MediaFoundationReader> inputFileReaders)
{
    // ...
    private int _readIndex = 0;
    // ...
        public int Read(byte[] buffer, int offset, int count)
        {
            int startIndex = _readIndex;
            int currentOffset = offset;
            int totalRead = 0;
            int remaining = count;

            // there should be a 'plus plus' operator here
            for (_readIndex = startIndex; _readIndex < _inputFileReaders.Count && remaining > 0; _readIndex++)
            {
                MediaFoundationReader currentReader = _inputFileReaders[_readIndex];

                int readNow = currentReader.Read(buffer, currentOffset, remaining);

                // there should be a 'plus equal' operator here
                currentOffset += readNow;   
                totalRead += readNow;
                remaining -= readNow;
            }

            return totalRead;
        }
}
We put some Debug logs (not shown in code) and here are some info about the read invocations, although they're not very clear to us:
Common format: 16 bit PCM: 44kHz 1 channels
Saving to WMA with explicit encoder
Read, offset: 0, count: 352800
  - readIndex: 0, currentOffset: 0, remaining: 352800
  - readIndex: 1, currentOffset: 117340, remaining: 235460
  - readIndex: 2, currentOffset: 147132, remaining: 205668
  - readIndex: 3, currentOffset: 172316, remaining: 180484
  - readIndex: 4, currentOffset: 232060, remaining: 120740
  - readIndex: 5, currentOffset: 261852, remaining: 90948
  - totalRead: 352800
Read, offset: 0, count: 352800
  - readIndex: 6, currentOffset: 0, remaining: 352800
  - totalRead: 133468
Read, offset: 0, count: 352800
  - totalRead: 0