C++、Android NDK:如何将原始音频数据正确保存到文件并再次加载

问题描述 投票:0回答:2

我正在开发一个播放音频的 Android 应用程序。为了最大限度地减少延迟,我通过 JNI 使用 C++ 来使用 C++ 库双簧管来播放应用程序。

目前,在播放之前,应用程序必须解码给定的文件(例如 mp3),然后播放解码的原始音频流。如果文件较大,这会导致播放开始之前需要等待一段时间。 所以我想预先进行解码,保存它,当请求播放时,只需播放保存文件中的解码数据。 我几乎不知道如何在 C++ 中进行正确的文件 i/o,并且很难理解它。有可能我的问题可以通过正确的库来解决,我不确定。

所以目前我正在保存我的文件,如下所示:

bool Converter::doConversion(const std::string& fullPath, const std::string& name) {

    // here I'm setting up the extractor and necessary inputs. Omitted since not relevant

    // this is where the decoder is called to decode a file to raw audio
    constexpr int kMaxCompressionRatio{12};
    const long maximumDataSizeInBytes = kMaxCompressionRatio * (size) * sizeof(int16_t);
    auto decodedData = new uint8_t[maximumDataSizeInBytes];

    int64_t bytesDecoded = NDKExtractor::decode(*extractor, decodedData);
    auto numSamples = bytesDecoded / sizeof(int16_t);
    auto outputBuffer = std::make_unique<float[]>(numSamples);

    // This block is necessary to get the correct format for oboe.
    // The NDK decoder can only decode to int16, we need to convert to floats
    oboe::convertPcm16ToFloat(
            reinterpret_cast<int16_t *>(decodedData),
            outputBuffer.get(),
            bytesDecoded / sizeof(int16_t));

    // This is how I currently save my outputBuffer to a file. This produces a file on the disc.
    std::string outputSuffix = ".pcm";
    std::string outputName = std::string(mFolder) + name + outputSuffix;
    std::ofstream outfile(outputName.c_str(), std::ios::out | std::ios::binary);
    outfile.write(reinterpret_cast<const char *>(&outputBuffer), sizeof outputBuffer);

    return true;
}

所以我相信我将 float 数组,将其转换为 char 数组并保存。我不确定这是否正确,但这是我对它的最好理解。 反正之后有一个文件。 编辑:正如我在分析保存的文件时发现的那样,我只存储了 8 个字节。

现在如何再次加载该文件并恢复我的outputBuffer的内容?

目前我有这一点,显然不完整:

StorageDataSource *StorageDataSource::openPCM(const char *fileName, AudioProperties targetProperties) {

    long bufferSize;
    char * buffer;

    std::ifstream stream(fileName, std::ios::in | std::ios::binary);

    stream.seekg (0, std::ios::beg);
    bufferSize = stream.tellg();
    buffer = new char [bufferSize];
    stream.read(buffer, bufferSize);
    stream.close();

如果这是正确的,我需要做什么才能将数据恢复为原始类型?如果我做错了,它如何以正确的方式工作?

android c++ io android-ndk oboe
2个回答
0
投票

感谢@Michael 的评论,我知道了如何做到这一点。

这就是我现在保存数据的方式:

bool Converter::doConversion(const std::string& fullPath, const std::string& name) {

    // here I'm setting up the extractor and necessary inputs. Omitted since not relevant

    // this is where the decoder is called to decode a file to raw audio
    constexpr int kMaxCompressionRatio{12};
    const long maximumDataSizeInBytes = kMaxCompressionRatio * (size) * sizeof(int16_t);
    auto decodedData = new uint8_t[maximumDataSizeInBytes];

    int64_t bytesDecoded = NDKExtractor::decode(*extractor, decodedData);
    auto numSamples = bytesDecoded / sizeof(int16_t);
 
    // converting to float has moved to the reading function, so now i save decodedData directly.

    std::string outputSuffix = ".pcm";
    std::string outputName = std::string(mFolder) + name + outputSuffix;
    std::ofstream outfile(outputName.c_str(), std::ios::out | std::ios::binary);
    
    outfile.write((char*)decodedData, numSamples * sizeof (int16_t));
    return true;
}

这就是我再次读取存储的文件的方式:

    long bufferSize;
    char * inputBuffer;

    std::ifstream stream;
    stream.open(fileName, std::ifstream::in | std::ifstream::binary);

    if (!stream.is_open()) {
        // handle error
    }

    stream.seekg (0, std::ios::end); // seek to the end
    bufferSize = stream.tellg(); // get size info, will be 0 without seeking to the end
    stream.seekg (0, std::ios::beg); // seek to beginning

    inputBuffer = new char [bufferSize];

    stream.read(inputBuffer, bufferSize); // the actual reading into the buffer. would be null without seeking back to the beginning
    stream.close();

    // done reading the file.
    
    auto numSamples = bufferSize / sizeof(int16_t); // calculate my number of samples, so the audio is correctly interpreted
    
    auto outputBuffer = std::make_unique<float[]>(numSamples);

    // the decoding bit now happens after the file is open. This avoids confusion
    // The NDK decoder can only decode to int16, we need to convert to floats
    oboe::convertPcm16ToFloat(
            reinterpret_cast<int16_t *>(inputBuffer),
            outputBuffer.get(),
            bufferSize / sizeof(int16_t));


    // here I continue working with my outputBuffer

我没有或没有得到的重要信息/理解 C++ 是

a) 指针的大小与其数据的大小不同 指向 和 b) 寻找流的工作原理。我需要把 在我在缓冲区中找到任何数据之前,针回到开始处。


0
投票

另一种选择是使用 libsndfile。我维护着这个被遗忘的分支,它使用 Oboe 库实现了这个库。例如,我在麦克风回调中使用它,如下所示:

class MAudioRecorderCallback : public DefaultDataCallback {
  oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream,
                                        void *audioData, int32_t numFrames) {
    auto result =
        DefaultDataCallback::onAudioReady(oboeStream, audioData, numFrames);

    auto *inputInts = static_cast<const int16_t *>(audioData);
    ...
      mSoundRecording->write(inputInts, numFrames * oboeStream->getChannelCount());
    ...
    return result;
  }

  AndroidAudioDeviceManager *m_aadm;
  SoundRecording* mSoundRecording = nullptr;

public:
  MAudioRecorderCallback(AndroidAudioDeviceManager *pManager)
      : m_aadm(pManager) {
    mSoundRecording = &m_aadm->mSoundRecording;
  }
};

文件将开始写入一个简单的:

mSoundRecording.initiateWritingToFile(path.c_str(), mOutputChannelCount, mSampleRate);

SoundRecording 类的完整实现是这里

© www.soinside.com 2019 - 2024. All rights reserved.