reuniware / OboeAudioRecorder

This is a sample project that uses Google Oboe Library for recording the MIC input of an Android device and write the recorded data to a .WAV file (works also in background).
24 stars 7 forks source link

Accessing numFramesRead back from MainActivity #1

Open febryituery opened 3 years ago

febryituery commented 3 years ago

Hi, First of all thank you for sharing this simple oboe audio recorder. i have a question regarding stream read data in oboe. is there any way to access numFramesRead in OboeAudioRecorder.cpp from MainActivity.kt. i need that data to processing inside async task.

thank you

Jepretan Layar 2020-12-01 pukul 10 20 07

reuniware commented 3 years ago

Yes you can do that by declaring the nbFramesRead before the declaration of the "StartAudioRecorder" function in the "OboeAudioRecorder.cpp" file, then you must declare it in the JNI interface (in "native-lib.cpp"), and then in your Java code or Kotlin code of the activity (in Kotlin as "external fun" in "MainActivity.kt").

Let me know when you will have done it successfully ;)

febryituery commented 3 years ago

Yes you can do that by declaring the nbFramesRead before the declaration of the "StartAudioRecorder" function in the "OboeAudioRecorder.cpp" file, then you must declare it in the JNI interface (in "native-lib.cpp"), and then in your Java code or Kotlin code of the activity (in Kotlin as "external fun" in "MainActivity.kt").

Let me know when you will have done it successfully ;)

okay, thank you for replying

febryituery commented 3 years ago

Hai, i have implemented something in code

in MainActivity.kt i added

external fun getNumFrames(): Int

in native-lib.cpp i added

extern "C" JNIEXPORT jint JNICALL
Java_com_example_oboeaudiorecorder_MainActivity_getNumFrames(
        JNIEnv* env,
        jobject /* this */) {
    static int32_t a = OboeAudioRecorder::getNumFramesRead();
    return a;
}

and in OboeAudioRecorder.cpp i added

// Created by DidierV on 29/06/2020.
//
#include <jni.h>
#include <string>
#include <oboe/Oboe.h>
#include "OboeAudioRecorder.h"
#include "oboe/samples/debug-utils/logging_macros.h"
#include <fstream>

namespace little_endian_io
{
    template <typename Word>
    std::ostream& write_word( std::ostream& outs, Word value, unsigned size = sizeof( Word ) )
    {
        for (; size; --size, value >>= 8)
            outs.put( static_cast <char> (value & 0xFF) );
        return outs;
    }
}
using namespace little_endian_io;

class OboeAudioRecorder: public oboe::AudioStreamCallback {

private:
    oboe::ManagedStream outStream;
    oboe::AudioStream *stream{};

    oboe::DataCallbackResult
    onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
        LOGE("onAudioReady");
    }

    static OboeAudioRecorder *singleton;
    explicit OboeAudioRecorder() = default;

public:
    static OboeAudioRecorder *get() {
        if (singleton == nullptr)
            singleton = new OboeAudioRecorder();
        return singleton;
    }

    bool isRecording = true;

    void StopAudioRecorder() {
        this->isRecording = false;
    }

    static int32_t nbFramesRead;

    int static getNumFramesRead(){
        return nbFramesRead;
    }

    void StartAudioRecorder(const char *fullPathToFile, const int recordingFreq) {
        this->isRecording = true;
        oboe::AudioStreamBuilder builder;
        builder.setDirection(oboe::Direction::Input);
        builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
//        builder.setPerformanceMode(oboe::PerformanceMode::None);
        builder.setFormat(oboe::AudioFormat::I16);
        builder.setChannelCount(oboe::ChannelCount::Mono);
        builder.setInputPreset(oboe::InputPreset::VoiceRecognition);
        builder.setSharingMode(oboe::SharingMode::Shared);
        builder.setSampleRate(recordingFreq);
        builder.setAudioApi(oboe::AudioApi::AAudio);
//        builder.setCallback(this);

        // Wave file generating stuff (from https://www.cplusplus.com/forum/beginner/166954/)
        int sampleRate = recordingFreq;
        int bitsPerSample = 16; // multiple of 8
        int numChannels = 1; // 2 for stereo, 1 for mono

        std::ofstream f;
        //const char *path = "/storage/emulated/0/Music/record.wav";
        const char *path = fullPathToFile;
        f.open(path, std::ios::binary);
        // Write the file headers
        f << "RIFF----WAVEfmt ";     // (chunk size to be filled in later)
        write_word( f,     16, 4 );  // no extension data
        write_word( f,      1, 2 );  // PCM - integer samples
        write_word( f,      numChannels, 2 );  // one channel (mono) or two channels (stereo file)
        write_word( f,  recordingFreq, 4 );  // samples per second (Hz)
        //write_word( f, 176400, 4 );  // (Sample Rate * BitsPerSample * Channels) / 8
        write_word( f,(recordingFreq * bitsPerSample * numChannels) / 8, 4 );  // (Sample Rate * BitsPerSample * Channels) / 8
        write_word( f,      4, 2 );  // data block size (size of two integer samples, one for each channel, in bytes)
        write_word( f,     bitsPerSample, 2 );  // number of bits per sample (use a multiple of 8)

        // Write the data chunk header
        size_t data_chunk_pos = f.tellp();
        f << "data----";  // (chunk size to be filled in later)
        // f.flush();

        // Write the audio samples
        constexpr double two_pi = 6.283185307179586476925286766559;
        constexpr double max_amplitude = 32760;  // "volume"

        oboe::Result r = builder.openStream(&stream);
        if (r != oboe::Result::OK) {
            return;
        }

        r = stream->requestStart();
        if (r != oboe::Result::OK) {
            return;
        }

        auto a = stream->getState();
        if (a == oboe::StreamState::Started) {

            constexpr int kMillisecondsToRecord = 2;
            auto requestedFrames = (int32_t) (kMillisecondsToRecord * (stream->getSampleRate() / oboe::kMillisPerSecond));
            __android_log_print(ANDROID_LOG_INFO, "OboeAudioRecorder", "requestedFrames = %d", requestedFrames);

            int16_t mybuffer[requestedFrames];
            constexpr int64_t kTimeoutValue = 3 * oboe::kNanosPerMillisecond;

            int framesRead = 0;
            do {
                auto result = stream->read(mybuffer, requestedFrames, 0);
                if (result != oboe::Result::OK) {
                    break;
                }
                framesRead = result.value();
                __android_log_print(ANDROID_LOG_INFO, "OboeAudioRecorder", "framesRead = %d", framesRead);
                if (framesRead > 0) {
                    break;
                }
            } while (framesRead != 0);

            while (isRecording) {
                auto result = stream->read(mybuffer, requestedFrames, kTimeoutValue * 1000);
                if (result == oboe::Result::OK) {
                    result.value();
                    __android_log_print(ANDROID_LOG_INFO, "OboeAudioRecorder", "nbFramesRead = %d", nbFramesRead);
                    for (int i = 0; i < nbFramesRead; i++) {
                        __android_log_print(ANDROID_LOG_INFO, "OboeAudioRecorder","nbFramesRead[%d] = %d", i, mybuffer[i]);
                        write_word( f, (int)(mybuffer[i]), 2 );
                        //write_word( f, (int)(mybuffer[i]), 2 ); // If stereo recording, add this line and write mybuffer[i+1] ?
                    }
                } else {
                    auto error = convertToText(result.error());
                    __android_log_print(ANDROID_LOG_INFO, "OboeAudioRecorder", "error = %s", error);
                }
            }

            stream->requestStop();
            stream->close();

            // (We'll need the final file size to fix the chunk sizes above)
            size_t file_length = f.tellp();

            // Fix the data chunk header to contain the data size
            f.seekp( data_chunk_pos + 4 );
            write_word( f, file_length - data_chunk_pos + 8 );

            // Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes
            f.seekp( 0 + 4 );
            write_word( f, file_length - 8, 4 );
            f.close();
        }
    }

};

OboeAudioRecorder *OboeAudioRecorder::singleton = nullptr;

but i still get an error like this

Error while executing process /Users/febrydwiputra/Library/Android/sdk/cmake/3.10.2.4988404/bin/ninja with arguments {-C /Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/.cxx/cmake/debug/arm64-v8a OboeAudioRecorder native-lib}
ninja: Entering directory `/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/.cxx/cmake/debug/arm64-v8a'
[1/1] Linking CXX shared library /Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
FAILED: /Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so 
: && /Users/febrydwiputra/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ --target=aarch64-none-linux-android21 --gcc-toolchain=/Users/febrydwiputra/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Users/febrydwiputra/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security   -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libnative-lib.so -o /Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so CMakeFiles/native-lib.dir/native-lib.cpp.o  oboe/liboboe.a /Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/build/intermediates/cmake/debug/obj/arm64-v8a/libOboeAudioRecorder.so -llog -llog -lOpenSLES -latomic -lm && :
CMakeFiles/native-lib.dir/native-lib.cpp.o: In function `OboeAudioRecorder::StartAudioRecorder(char const*, int)':
/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/src/main/cpp/OboeAudioRecorder.cpp:136: undefined reference to `OboeAudioRecorder::nbFramesRead'
/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/src/main/cpp/OboeAudioRecorder.cpp:136: undefined reference to `OboeAudioRecorder::nbFramesRead'
/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/src/main/cpp/OboeAudioRecorder.cpp:137: undefined reference to `OboeAudioRecorder::nbFramesRead'
/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/src/main/cpp/OboeAudioRecorder.cpp:137: undefined reference to `OboeAudioRecorder::nbFramesRead'
CMakeFiles/native-lib.dir/native-lib.cpp.o: In function `OboeAudioRecorder::getNumFramesRead()':
/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/src/main/cpp/OboeAudioRecorder.cpp:(.text._ZN17OboeAudioRecorder16getNumFramesReadEv[_ZN17OboeAudioRecorder16getNumFramesReadEv]+0x0): undefined reference to `OboeAudioRecorder::nbFramesRead'
CMakeFiles/native-lib.dir/native-lib.cpp.o:/Users/febrydwiputra/AndroidStudioProjects/OboeAudioRecorder/app/src/main/cpp/OboeAudioRecorder.cpp:(.text._ZN17OboeAudioRecorder16getNumFramesReadEv[_ZN17OboeAudioRecorder16getNumFramesReadEv]+0x4): more undefined references to `OboeAudioRecorder::nbFramesRead' follow
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.

am i missing something?i'm sorry, i'm new with android NDK

reuniware commented 3 years ago

I think that you should write "static int getNumFramesRead()" in this function declaration.