google / oboe

Oboe is a C++ library that makes it easy to build high-performance audio apps on Android.
Apache License 2.0
3.7k stars 566 forks source link

.wav to Audio Stream #548

Closed luismartimor closed 4 years ago

luismartimor commented 5 years ago

I think we need more examples. I'm having difficulties in loading just a .wav into a stream. Would be nice to have a simple sample code for just a mixer with two bus streams, like Apple had with MixerHost.

atneya commented 5 years ago

Thanks for the suggestion.

What difficulties are you encountering in loading a .wav into a stream? The RhythmGame sample has examples in loading a mp3 to a stream if that could be of assistance.

A mixer sample sounds like a good idea, what exactly do you mean by two bus streams? For an example on mixing square waves, see MegaDrone.

luismartimor commented 5 years ago

Thanks for the answer, atneya. Ok, I'm testing with RhythmGame, gonna try.

Two streams, I mean just two instruments audio files looped. Drum loop and a guitar riff, for example. And a mixer. Just to see how to stream and mix more than one audio file, like a very simple mixer. With the ability to start / stop any one of these loops.

It would be very helpful

atneya commented 5 years ago

Thanks for the suggestion, we are always looking for examples that are helpful to start out from.

luismartimor commented 5 years ago

Well... trying to replicate a simple audio bus from RhythmGame...

I think these samples are more complicated than should be, cause the main AudioStreamCallback class ( Game ) is very dependent on other classes ( AAsetDataSource, NDKExtractor, OpenGLFunctions, UtilityFunctions, and all linking with others ... ) complicating, specially for newbies in Android C++.

I think best sample code examples are the simplest and direct ones.

luismartimor commented 5 years ago

The RhythmGame uses compressed .mp3 files, extracted with AAssetDataSource::newFromCompressedAsset, with FFMpegExtractor, NDKExtractor ...

To get better sound quality need to use .wav or .aiff files, any library to get these instead of mp3 ?

Need an example to get data from .wav / .aiff files to the Player in a direct clear way.

luismartimor commented 5 years ago

The RhythmGame uses compressed .mp3 files, extrated with AAssetDataSource::newFromCompressedAsset, with FFMpegExtractor, NDKExtractor ...

To get better sound quality need to use .wav or .aiff files, any library to get these instead of mp3 ?

Need an example to get data from .wav / .aiff files to the Player in a direct clear way.

philburk commented 5 years ago

Oboe does not support sound file I/O. You could use libsndfile. http://www.mega-nerd.com/libsndfile/

Or you could adapt something like the JSyn WAV and AIFF file parsers. https://github.com/philburk/jsyn/tree/master/src/com/jsyn/util/soundfile

luismartimor commented 5 years ago

Oboe does not support sound file I/O. You could use libsndfile. http://www.mega-nerd.com/libsndfile/

Or you could adapt something like the JSyn WAV and AIFF file parsers. https://github.com/philburk/jsyn/tree/master/src/com/jsyn/util/soundfile

Thanks philburk! In c ? ups .. it's gonna be harder than i thought ... Just read a .wav file to the stream. Any example code ?

atneya commented 5 years ago

Most sound file I/O libraries will have example code for decoding and encoding lossless. Decoding files are generally a very finicky task that should be left to libraries.

Libsndfile, which is used in PortAudio is a good general purpose I/O library. An example of reading and writing a WAV file can be found here: https://github.com/erikd/libsndfile/blob/master/examples/sfprocess.c

Using the library in a C++ should be as simple as using an extern C. I also believe libsndfile now has CMake support, we should make it easy to import into an existing NDK project.

luismartimor commented 5 years ago

Most sound file I/O libraries will have example code for decoding and encoding lossless. Decoding files are generally a very finicky task that should be left to libraries.

Libsndfile, which is used in PortAudio is a good general purpose I/O library. An example of reading and writing a WAV file can be found here: https://github.com/erikd/libsndfile/blob/master/examples/sfprocess.c

Using the library in a C++ should be as simple as using an extern C. I also believe libsndfile now has CMake support, we should make it easy to import into an existing NDK project.

How nice would be a sample example of it ... Many barriers to entry for developers not used to work with C++ on android

luismartimor commented 5 years ago

Oboe does not support sound file I/O. You could use libsndfile. http://www.mega-nerd.com/libsndfile/

Or you could adapt something like the JSyn WAV and AIFF file parsers. https://github.com/philburk/jsyn/tree/master/src/com/jsyn/util/soundfile

@dturner explains FFmpeg audio decoding here https://medium.com/@donturner/using-ffmpeg-for-faster-audio-decoding-967894e94e71

What is convenient to read .wav/aiff data in Oboe , libsndfile or FFmpeg ?

philburk commented 5 years ago

FFmpeg is for reading MP3 files. libsndfile is for WAV and AIFF

If you just have a few files then you could export the WAV file from Audacity as raw int16. Then you can just load that directly from the raw file into your app. Make sure the endianness matches.

mgood7123 commented 5 years ago

you could try using https://github.com/jniemann66/ReSampler, I just got it working successfully on android a few hours ago https://github.com/jniemann66/ReSampler/issues/8#issuecomment-509023082

however it is an IMPORTANT note that Output file quality takes precedence over all other considerations, such as processing speed (although efforts to improve the latter will certainly be explored, they cannot be at the expense of the former)

"ok, this can now successfully resample a 44100khz 16 bit int wav file to 48000khz 16 bit int raw audio data file

E/OboeAudio: Started conversion at 1.56253E+12 milliseconds
I/ReSampler: 2.0.7 64-bit version
I/ReSampler: Input file: /sdcard/ReSampler/00001313.wav
I/ReSampler: Output file: /data/user/0/media.player.pro/files/00001313_48000.raw
I/ReSampler: Changing output bit format to 16
I/ReSampler: Changing output file format to raw
I/ReSampler: input bit format: 16
I/ReSampler: source file channels: 2
I/ReSampler: input sample rate: 44100
    output sample rate: 48000
I/ReSampler: Scanning input file for peaks ...Done
    Peak input sample: 0.999969 (-0.000265 dBFS) at 0:0:1.259297
I/ReSampler: LPF transition frequency: 20045.45 Hz (90.91 %)
I/ReSampler: Conversion ratio: 1.088435 (160:147)
I/ReSampler: Stage: 1
    inputRate: 44100
    outputRate: 63000
    ft: 20045.454545
    stopFreq: 22050.000000
    transition width: 3.181818 %
    guarantee: 22050.000000
    Generated Filter Size: 2943
    Output Buffer Size: 46812
I/ReSampler: Stage: 2
    inputRate: 63000
    outputRate: 48000
    ft: 20045.454545
    stopFreq: 25950.000000
    transition width: 9.090909 %
    guarantee: 25950.000000
    Generated Filter Size: 2163
    Output Buffer Size: 35666
I/ReSampler: Command lines to do this conversion in discreet steps:
     -i /sdcard/ReSampler/00001313.wav -o /data/user/0/media.player.pro/files/00001313_48000-stage1.raw -r 63000 --lpf-cutoff 96.818182 --lpf-transition 3.181818 --maxStages 1
     -i /data/user/0/media.player.pro/files/00001313_48000-stage1.raw -o /data/user/0/media.player.pro/files/00001313_48000.raw -r 48000 --lpf-cutoff 90.909091 --lpf-transition 9.090909 --maxStages 1
I/ReSampler: Stage: 1
    inputRate: 44100
    outputRate: 63000
    ft: 20045.454545
    stopFreq: 22050.000000
    transition width: 3.181818 %
    guarantee: 22050.000000
    Generated Filter Size: 2943
    Output Buffer Size: 46812
I/ReSampler: Stage: 2
    inputRate: 63000
    outputRate: 48000
    ft: 20045.454545
    stopFreq: 25950.000000
    transition width: 9.090909 %
    guarantee: 25950.000000
    Generated Filter Size: 2163
    Output Buffer Size: 35666
I/ReSampler: Command lines to do this conversion in discreet steps:
     -i /sdcard/ReSampler/00001313.wav -o /data/user/0/media.player.pro/files/00001313_48000-stage1.raw -r 63000 --lpf-cutoff 96.818182 --lpf-transition 3.181818 --maxStages 1
     -i /data/user/0/media.player.pro/files/00001313_48000-stage1.raw -o /data/user/0/media.player.pro/files/00001313_48000.raw -r 48000 --lpf-cutoff 90.909091 --lpf-transition 9.090909 --maxStages 1
I/ReSampler: Writing Metadata
I/ReSampler: Converting (multi-stage, multi-threaded) ...
E/OboeAudio: Ended conversion at 1.56253E+12 milliseconds
E/OboeAudio: TIME took 10902.4 milliseconds
E/OboeAudio: /sdcard/ReSampler/00001313.wav file size: 4123400
E/OboeAudio: /data/user/0/media.player.pro/files/00001313_48000.raw file size: 4487020
D/OboeAudio: Opened backing track
D/OboeAudio: length in human time:                              23:369:895:833
D/OboeAudio: length in nanoseconds:                             2.33699E+10
D/OboeAudio: length in microseconds:                            2.33699E+07
D/OboeAudio: length in milliseconds:                            23369.9
D/OboeAudio: length in seconds:                                 23.3699
D/OboeAudio: length in minutes:                                 0.389498
D/OboeAudio: length in hours:                                   0.00649164
D/OboeAudio: bytes:                                             4487864
D/OboeAudio: frames:                                            1121755
D/OboeAudio: sample rate:                                       48000
D/OboeAudio: length of 1 frame at 48000 sample rate:
D/OboeAudio: Human Time:                                        20:833
D/OboeAudio: Nanoseconds:                                       833.333
D/OboeAudio: Microseconds:                                      20.8333
D/OboeAudio: Milliseconds:                                      0
D/OboeAudio: Seconds:                                           0
D/OboeAudio: Minutes:                                           0
D/OboeAudio: Hours:                                             0
// reads a entire file
size_t read__(char *file, char **p) {
    int fd;
    size_t len = 0;
    char *o = NULL;
    *p = NULL;
    if (!(fd = open(file, O_RDONLY)))
    {
        std::cerr << "open() failure" << std::endl;
        return 0;
    }
    len = static_cast<size_t>(lseek(fd, 0, SEEK_END));
    lseek(fd, 0, 0);
    if (!(o = static_cast<char *>(malloc(len)))) {
        std::cerr << "failure to malloc()" << std::endl;
    }
    if ((read(fd, o, len)) == -1) {
        std::cerr << "failure to read()" << std::endl;
    }
    int cl = close(fd);
    if (cl < 0) {
        std::cerr << "cannot close \"" << file << "\", returned " << cl << std::endl;
        return 0;
    }
    *p = o;
    return len;
}

SoundRecording * SoundRecording::loadFromAssets(AAssetManager *assetManager, const char *filename, int SampleRate, int mChannelCount) {

    // Load the backing track
    AAsset* asset = AAssetManager_open(assetManager, filename, AASSET_MODE_BUFFER);

    if (asset == nullptr){
        LOGE("Failed to open track, filename %s", filename);
        return nullptr;
    }

    // Get the length of the track (we assume it is stereo 48kHz)
    uint64_t trackSize = static_cast<uint64_t>(AAsset_getLength(asset));

    // Load it into memory
    const int16_t *audioBuffer = static_cast<const int16_t*>(AAsset_getBuffer(asset));
    if (audioBuffer == nullptr){
        LOGE("Could not get buffer for track");
        return nullptr;
    }
    const int actualSampleRate = 48000;
    const int actualChannelCount = 2;
    const char * infile = "/sdcard/ReSampler/00001313.wav";
    const char * outfile = static_cast<std::string>(TEMPDIR + "/00001313_48000.raw").c_str();
    extern int main(int argc, char * argv[]);
    const int argc = 12;
    const char *argv[argc];
    argv[0] = "ReSampler";
    argv[1] = "-i";
    argv[2] = infile;
    argv[3] = "-o";
    argv[4] = outfile;
    argv[5] = "-r";
    argv[6] = "48000";
    argv[7] = "-b";
    argv[8] = "16";
    argv[9] = "--showStages";
    argv[10] = "--mt";
    argv[11] = "--noTempFile";
    double s = now_ms();
    LOGE("Started conversion at %G milliseconds", s);
    main(argc, const_cast<char **>(argv));
    double e = now_ms();
    LOGE("Ended conversion at %G milliseconds", e);
    LOGE("TIME took %G milliseconds", e - s);

    // read the file into memory
    char * in = nullptr;
    size_t insize = 0;
    char * out = nullptr;
    size_t outsize = 0;
    insize = read__(const_cast<char *>(infile), &in);
    outsize = read__(const_cast<char *>(outfile), &out);

    LOGE("%s file size: %zu", infile, insize);
    LOGE("%s file size: %zu", outfile, outsize);

    const uint64_t totalFrames = outsize / (2 * actualChannelCount);
    WAVEFORMAUDIODATATOTALFRAMES = totalFrames;
    WAVEFORMAUDIODATA = reinterpret_cast<const int16_t *>(out);

    SoundRecordingAudioData * AudioData = new SoundRecordingAudioData(totalFrames, mChannelCount, SampleRate);
    AudioTime * allFrames = new AudioTime();
    allFrames->update(totalFrames, AudioData);
    LOGD("Opened backing track");
    LOGD("length in human time:                              %s", allFrames->format(true).c_str());
    LOGD("length in nanoseconds:                             %G", allFrames->nanosecondsTotal);
    LOGD("length in microseconds:                            %G", allFrames->microsecondsTotal);
    LOGD("length in milliseconds:                            %G", allFrames->millisecondsTotal);
    LOGD("length in seconds:                                 %G", allFrames->secondsTotal);
    LOGD("length in minutes:                                 %G", allFrames->minutesTotal);
    LOGD("length in hours:                                   %G", allFrames->hoursTotal);
    LOGD("bytes:                                             %ld", trackSize);
    LOGD("frames:                                            %ld", totalFrames);
    LOGD("sample rate:                                       %d", SampleRate);
    LOGD("length of 1 frame at %d sample rate:", SampleRate);
    LOGD("Human Time:                                        %s", AudioData->TimeTruncated);
    LOGD("Nanoseconds:                                       %G", AudioData->nanosecondsPerFrame);
    LOGD("Microseconds:                                      %G", AudioData->microsecondsPerFrame);
    LOGD("Milliseconds:                                      %G", AudioData->millisecondsPerFrame);
    LOGD("Seconds:                                           %G", AudioData->secondsPerFrame);
    LOGD("Minutes:                                           %G", AudioData->minutesPerFrame);
    LOGD("Hours:                                             %G", AudioData->hoursPerFrame);
    return new SoundRecording(reinterpret_cast<int16_t *>(out), AudioData);
}

"

android-usb commented 4 years ago

Completed with DrumThumper app