Android Audio代码分析(5): AudioRecord::getMinBufferSize

getMinBufferSizeJava接口

getMinBufferSize是AudioRecord和AudioTrack的static 接口, AudioRecord/AudioTrack中的实现是不同的,先分析AudioRecord的getMinBufferSize.

AudioRecord::getMinBufferSize

frameworks/base/media/java/android/media/AudioRecord.java


public class AudioRecord implements AudioRouting, MicrophoneDirection,
        AudioRecordingMonitor, AudioRecordingMonitorClient
{
    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
        int channelCount = 0;
        switch (channelConfig) {
        case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
        case AudioFormat.CHANNEL_IN_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            channelCount = 1;
            break;
        case AudioFormat.CHANNEL_IN_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
        case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):
            channelCount = 2;
            break;
        case AudioFormat.CHANNEL_IN_5POINT1:
            channelCount = 6;
            break;
        case AudioFormat.CHANNEL_INVALID:
        default:
            loge("getMinBufferSize(): Invalid channel configuration.");
            return ERROR_BAD_VALUE;
        }

        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
        if (size == 0) {
            return ERROR_BAD_VALUE;
        }
        else if (size == -1) {
            return ERROR;
        }
        else {
            return size;
        }
    }
}

AudioTrack::getMinBufferSize


public class AudioTrack extends PlayerBase
                        implements AudioRouting
                                 , VolumeAutomation
{

    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
        int channelCount = 0;
        switch(channelConfig) {
        case AudioFormat.CHANNEL_OUT_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            channelCount = 1;
            break;
        case AudioFormat.CHANNEL_OUT_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
            channelCount = 2;
            break;
        default:
            if (!isMultichannelConfigSupported(channelConfig)) {
                loge("getMinBufferSize(): Invalid channel configuration.");
                return ERROR_BAD_VALUE;
            } else {
                channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
            }
        }

        if (!AudioFormat.isPublicEncoding(audioFormat)) {
            loge("getMinBufferSize(): Invalid audio format.");
            return ERROR_BAD_VALUE;
        }

        // sample rate, note these values are subject to change
        // Note: AudioFormat.SAMPLE_RATE_UNSPECIFIED is not allowed
        if ( (sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN) ||
                (sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) ) {
            loge("getMinBufferSize(): " + sampleRateInHz + " Hz is not a supported sample rate.");
            return ERROR_BAD_VALUE;
        }

        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
        if (size <= 0) {
            loge("getMinBufferSize(): error querying hardware");
            return ERROR;
        }
        else {
            return size;
        }
    }

}

native_get_min_buffer_size

frameworks/base/core/jni/android_media_AudioRecord.cpp
    {"native_get_min_buff_size",
                             "(III)I",   (void *)android_media_AudioRecord_get_min_buff_size},

frameworks/base/core/jni/android_media_AudioTrack.cpp
   {"native_get_min_buff_size", 
                 "(III)I",   (void *)android_media_AudioTrack_get_min_buff_size},

AudioRecord::getMinBufferSize代码流程


java app code:

 AudioRecord.getMinBufferSize(16000,
                        AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

JNI


frameworks/base/core/jni/android_media_AudioRecord.cpp
static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint channelCount, jint audioFormat) {

    ALOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)",
          sampleRateInHertz, channelCount, audioFormat);

    size_t frameCount = 0;
    audio_format_t format = audioFormatToNative(audioFormat);
    status_t result = AudioRecord::getMinFrameCount(&frameCount,
            sampleRateInHertz,
            format,
            audio_channel_in_mask_from_count(channelCount));

    return frameCount * channelCount * audio_bytes_per_sample(format);
}

// 把java层的 AudioFormat.ENCODING_PCM_xxx 转换成 native 层的 AUDIO_FORMAT_PCM_xxx
static inline audio_format_t audioFormatToNative(int audioFormat)
{
    switch (audioFormat) {
    case ENCODING_PCM_24BIT:
        return AUDIO_FORMAT_PCM_24_BIT_PACKED;
    case ENCODING_PCM_16BIT:
        return AUDIO_FORMAT_PCM_16_BIT;
    case ENCODING_PCM_8BIT:
        return AUDIO_FORMAT_PCM_8_BIT;
}

// 获得 channel mask
static inline audio_channel_mask_t audio_channel_in_mask_from_count(uint32_t channel_count)
{
    uint32_t bits;
    switch (channel_count) {
    case 0:
        return AUDIO_CHANNEL_NONE;
    case 1:
        bits = AUDIO_CHANNEL_IN_MONO;
        break;
    case 2:
        bits = AUDIO_CHANNEL_IN_STEREO;
        break;
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
        // FIXME FCC_8
        return audio_channel_mask_for_index_assignment_from_count(channel_count);
    default:
        return AUDIO_CHANNEL_INVALID;
    }
    return audio_channel_mask_from_representation_and_bits(
            AUDIO_CHANNEL_REPRESENTATION_POSITION, bits);
}

Native lib

libaudioclient/AudioRecord.cpp


frameworks/av/media/libaudioclient/AudioRecord.cpp
status_t AudioRecord::getMinFrameCount(
        size_t* frameCount,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask)
{
    if (frameCount == NULL) {
        return BAD_VALUE;
    }

    size_t size;
    status_t status = AudioSystem::getInputBufferSize(sampleRate, format, channelMask, &size);
    if (status != NO_ERROR) {
        ALOGE("%s(): AudioSystem could not query the input buffer size for"
              " sampleRate %u, format %#x, channelMask %#x; status %d",
               __func__, sampleRate, format, channelMask, status);
        return status;
    }

    // We double the size of input buffer for ping pong use of record buffer.
    // Assumes audio_is_linear_pcm(format)
    if ((*frameCount = (size * 2) / (audio_channel_count_from_in_mask(channelMask) *
            audio_bytes_per_sample(format))) == 0) {
        ALOGE("%s(): Unsupported configuration: sampleRate %u, format %#x, channelMask %#x",
                __func__, sampleRate, format, channelMask);
        return BAD_VALUE;
    }

    return NO_ERROR;
}


AudioSystem::getInputBufferSize


status_t AudioSystem::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
        audio_channel_mask_t channelMask, size_t* buffSize)
{
    const sp<AudioFlingerClient> afc = getAudioFlingerClient();
    if (afc == 0) {
        return NO_INIT;
    }
    return afc->getInputBufferSize(sampleRate, format, channelMask, buffSize);
}

const sp<AudioSystem::AudioFlingerClient> AudioSystem::getAudioFlingerClient()
{
    // calling get_audio_flinger() will initialize gAudioFlingerClient if needed
    const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
    if (af == 0) return 0;
    Mutex::Autolock _l(gLock);
    return gAudioFlingerClient;
}

// establish binder interface to AudioFlinger service
const sp<IAudioFlinger> AudioSystem::get_audio_flinger()
{
    sp<IAudioFlinger> af;
    sp<AudioFlingerClient> afc;
    bool reportNoError = false;
    {
        Mutex::Autolock _l(gLock);
        if (gAudioFlinger == 0) {
            sp<IServiceManager> sm = defaultServiceManager();
            sp<IBinder> binder;
            do {
                binder = sm->getService(String16("media.audio_flinger"));
                if (binder != 0)
                    break;
                ALOGW("AudioFlinger not published, waiting...");
                usleep(500000); // 0.5 s
            } while (true);
            if (gAudioFlingerClient == NULL) {
                gAudioFlingerClient = new AudioFlingerClient();
            } else {
                reportNoError = true;
            }
            binder->linkToDeath(gAudioFlingerClient);
            gAudioFlinger = interface_cast<IAudioFlinger>(binder);
            LOG_ALWAYS_FATAL_IF(gAudioFlinger == 0);
            afc = gAudioFlingerClient;
            // Make sure callbacks can be received by gAudioFlingerClient
            ProcessState::self()->startThreadPool();
        }
        af = gAudioFlinger;
    }
    if (afc != 0) {
        int64_t token = IPCThreadState::self()->clearCallingIdentity();
        af->registerClient(afc);
        IPCThreadState::self()->restoreCallingIdentity(token);
    }
    if (reportNoError) reportError(NO_ERROR);
    return af;
}

class AudioSystem内部类 AudioFlingerClient
    class AudioFlingerClient: public IBinder::DeathRecipient, public BnAudioFlingerClient
    {
    public:
        AudioFlingerClient() :
            mInBuffSize(0), mInSamplingRate(0),
            mInFormat(AUDIO_FORMAT_DEFAULT), mInChannelMask(AUDIO_CHANNEL_NONE) {
 }

AudioFlingerClient::getInputBufferSize


status_t AudioSystem::AudioFlingerClient::getInputBufferSize(
                                                uint32_t sampleRate, audio_format_t format,
                                                audio_channel_mask_t channelMask, size_t* buffSize)
{
    const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
    if (af == 0) {
        return PERMISSION_DENIED;
    }
    Mutex::Autolock _l(mLock);
    // Do we have a stale mInBuffSize or are we requesting the input buffer size for new values
    if ((mInBuffSize == 0) || (sampleRate != mInSamplingRate) || (format != mInFormat)
        || (channelMask != mInChannelMask)) {
        size_t inBuffSize = af->getInputBufferSize(sampleRate, format, channelMask);
        if (inBuffSize == 0) {
            ALOGE("AudioSystem::getInputBufferSize failed sampleRate %d format %#x channelMask %#x",
                    sampleRate, format, channelMask);
            return BAD_VALUE;
        }
        // A benign race is possible here: we could overwrite a fresher cache entry
        // save the request params
        mInSamplingRate = sampleRate;
        mInFormat = format;
        mInChannelMask = channelMask;

        mInBuffSize = inBuffSize;
    }

    *buffSize = mInBuffSize;

    return NO_ERROR;
}

frameworks/av/media/libaudioclient/IAudioFlinger.cpp


    virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format,
            audio_channel_mask_t channelMask) const
    {
        Parcel data, reply;
        data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
        data.writeInt32(sampleRate);
        data.writeInt32(format);
        data.writeInt32(channelMask);
        remote()->transact(GET_INPUTBUFFERSIZE, data, &reply);
        return reply.readInt64();
    }

AudioFlinger service binder接口实现

有点不解的是 GET_INPUTBUFFERSIZE的实现也是在文件 IAudioFlinger.cpp 中
        case GET_INPUTBUFFERSIZE: {
            CHECK_INTERFACE(IAudioFlinger, data, reply);
            uint32_t sampleRate = data.readInt32();
            audio_format_t format = (audio_format_t) data.readInt32();
            audio_channel_mask_t channelMask = data.readInt32();
            reply->writeInt64( getInputBufferSize(sampleRate, format, channelMask) );
            return NO_ERROR;
        } break;

AudioFlinger service 实现

frameworks/av/services/audioflinger/AudioFlinger.cpp

size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
        audio_channel_mask_t channelMask) const
{
    status_t ret = initCheck();
    if (ret != NO_ERROR) {
        return 0;
    }
    if ((sampleRate == 0) ||
            !audio_is_valid_format(format) || !audio_has_proportional_frames(format) ||
            !audio_is_input_channel(channelMask)) {
        return 0;
    }

    AutoMutex lock(mHardwareLock);
    if (mPrimaryHardwareDev == nullptr) {
        return 0;
    }
    mHardwareStatus = AUDIO_HW_GET_INPUT_BUFFER_SIZE;

    sp<DeviceHalInterface> dev = mPrimaryHardwareDev->hwDevice();
    std::vector<audio_channel_mask_t> channelMasks = {channelMask};
    if (channelMask != AUDIO_CHANNEL_IN_MONO)
        channelMasks.push_back(AUDIO_CHANNEL_IN_MONO);
    if (channelMask != AUDIO_CHANNEL_IN_STEREO)
        channelMasks.push_back(AUDIO_CHANNEL_IN_STEREO);

    std::vector<audio_format_t> formats = {format};
    if (format != AUDIO_FORMAT_PCM_16_BIT)
        formats.push_back(AUDIO_FORMAT_PCM_16_BIT);

    std::vector<uint32_t> sampleRates = {sampleRate};
    static const uint32_t SR_44100 = 44100;
    static const uint32_t SR_48000 = 48000;

    if (sampleRate != SR_48000)
        sampleRates.push_back(SR_48000);
    if (sampleRate != SR_44100)
        sampleRates.push_back(SR_44100);

    mHardwareStatus = AUDIO_HW_IDLE;

    // Change parameters of the configuration each iteration until we find a
    // configuration that the device will support.
    audio_config_t config = AUDIO_CONFIG_INITIALIZER;
    for (auto testChannelMask : channelMasks) {
        config.channel_mask = testChannelMask;
        for (auto testFormat : formats) {
            config.format = testFormat;
            for (auto testSampleRate : sampleRates) {
                config.sample_rate = testSampleRate;

                size_t bytes = 0;
                status_t result = dev->getInputBufferSize(&config, &bytes);
                if (result != OK || bytes == 0) {
                    continue;
                }

                if (config.sample_rate != sampleRate || config.channel_mask != channelMask ||
                    config.format != format) {
                    uint32_t dstChannelCount = audio_channel_count_from_in_mask(channelMask);
                    uint32_t srcChannelCount =
                        audio_channel_count_from_in_mask(config.channel_mask);
                    size_t srcFrames =
                        bytes / audio_bytes_per_frame(srcChannelCount, config.format);
                    size_t dstFrames = destinationFramesPossible(
                        srcFrames, config.sample_rate, sampleRate);
                    bytes = dstFrames * audio_bytes_per_frame(dstChannelCount, format);
                }
                return bytes;
            }
        }
    }

    ALOGW("getInputBufferSize failed with minimum buffer size sampleRate %u, "
              "format %#x, channelMask %#x",sampleRate, format, channelMask);
    return 0;
}

DeviceHalInterface: AudioFlinger service 调用HalService

frameworks/av/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
    // Returns audio input buffer size according to parameters passed.
    virtual status_t getInputBufferSize(const struct audio_config *config,
            size_t *size) = 0;


frameworks/av/media/libaudiohal/impl/DeviceHalHidl.cpp
status_t DeviceHalHidl::getInputBufferSize(
        const struct audio_config *config, size_t *size) {
    if (mDevice == 0) return NO_INIT;
    AudioConfig hidlConfig;
    HidlUtils::audioConfigFromHal(*config, &hidlConfig);
    Result retval;
    Return<void> ret = mDevice->getInputBufferSize(
            hidlConfig,
            [&](Result r, uint64_t bufferSize) {
                retval = r;
                if (retval == Result::OK) {
                    *size = static_cast<size_t>(bufferSize);
                }
            });
    return processReturn("getInputBufferSize", ret, retval);
}


hardware/interfaces/audio/6.0/IDevice.hal
    /**
     * Returns audio input buffer size according to parameters passed or
     * INVALID_ARGUMENTS if one of the parameters is not supported.
     *
     * @param config audio configuration.
     * @return retval operation completion status.
     * @return bufferSize input buffer size in bytes.
     */
    getInputBufferSize(AudioConfig config)
            generates (Result retval, uint64_t bufferSize);

audio hal service实现

hardware/interfaces/audio/core/all-versions/default/PrimaryDevice.cpp
Return<void> PrimaryDevice::getInputBufferSize(const AudioConfig& config,
                                               getInputBufferSize_cb _hidl_cb) {
    return mDevice->getInputBufferSize(config, _hidl_cb);
}

Return<void> Device::getInputBufferSize(const AudioConfig& config, getInputBufferSize_cb _hidl_cb) {
    audio_config_t halConfig;
    HidlUtils::audioConfigToHal(config, &halConfig);
    size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
    Result retval(Result::INVALID_ARGUMENTS);
    uint64_t bufferSize = 0;
    if (halBufferSize != 0) {
        retval = Result::OK;
        bufferSize = halBufferSize;
    }
    _hidl_cb(retval, bufferSize);
    return Void();
}

audioHalService  加载hal so库

vendor/qcom/opensource/audio-hal/primary-hal/hal/audio_hw.c
static size_t get_input_buffer_size(uint32_t sample_rate,
                                    audio_format_t format,
                                    int channel_count,
                                    bool is_low_latency)
{
    /* Don't know if USB HIFI in this context so use true to be conservative */
    if (check_input_parameters(sample_rate, format, channel_count,
                               true /*is_usb_hifi */) != 0)
        return 0;

    return get_stream_buffer_size(AUDIO_CAPTURE_PERIOD_DURATION_MSEC,
                                  sample_rate,
                                  format,
                                  channel_count,
                                  is_low_latency);
}


static size_t get_stream_buffer_size(size_t duration_ms,
                                     uint32_t sample_rate,
                                     audio_format_t format,
                                     int channel_count,
                                     bool is_low_latency)
{
    size_t size = 0;
    uint32_t bytes_per_period_sample = 0;

    size = (sample_rate * duration_ms) / 1000;
    if (is_low_latency)
        size = configured_low_latency_capture_period_size;

    bytes_per_period_sample = audio_bytes_per_sample(format) * channel_count;
    size *= audio_bytes_per_sample(format) * channel_count;

    /* make sure the size is multiple of 32 bytes and additionally multiple of
     * the frame_size (required for 24bit samples and non-power-of-2 channel counts)
     * At 48 kHz mono 16-bit PCM:
     *  5.000 ms = 240 frames = 15*16*1*2 = 480, a whole multiple of 32 (15)
     *  3.333 ms = 160 frames = 10*16*1*2 = 320, a whole multiple of 32 (10)
     * Also, make sure the size is multiple of bytes per period sample
     */
    size = nearest_multiple(size, lcm(32, bytes_per_period_sample));

    return size;
}

简言之

client AudioRecord 通过AudioSystem 获得AudioFlinger的代理调用AudioFlinger Service, 而AudioFlinger service 又使用IDevice的代理 调用hardware.audio.service的服务,而hardware.audio.service的实现是通过加载 硬件相关的hal.so 实现的。对getMinBuffer这个相对简单的实现而言并没有设计audiopolicy 和stream等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Android 音频频谱图需要用到 Android 的 `AudioRecord` 类和 `FFT`(快速傅里叶变换)算法。下面是一个简单的示例代码: ```java import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.util.Arrays; public class MainActivity extends AppCompatActivity { private static final String TAG = "AudioRecord"; private static final int SAMPLE_RATE = 44100; // 采样率 private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; // 声道 private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; // 采样精度 private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); // 缓冲区大小 private static final int MSG_UPDATE_VIEW = 1; // 更新 UI 的消息 private AudioRecord mAudioRecord; private boolean mIsRecording; private Handler mHandler; private FFT mFft; private TextView mTextView; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.text_view); mButton = findViewById(R.id.button); mFft = new FFT(BUFFER_SIZE); mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_VIEW: updateView((double[]) msg.obj); break; } } }; } public void onClick(View view) { if (mIsRecording) { stopRecording(); } else { startRecording(); } } private void startRecording() { mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE); mAudioRecord.startRecording(); mIsRecording = true; mButton.setText(R.string.stop_recording); new Thread(new Runnable() { @Override public void run() { short[] buffer = new short[BUFFER_SIZE]; double[] fftBuffer = new double[BUFFER_SIZE]; while (mIsRecording) { int readSize = mAudioRecord.read(buffer, 0, BUFFER_SIZE); for (int i = 0; i < readSize; i++) { fftBuffer[i] = buffer[i] / 32768.0; } mFft.fft(fftBuffer); double[] spectrum = new double[BUFFER_SIZE / 2]; for (int i = 0; i < spectrum.length; i++) { double re = fftBuffer[i * 2]; double im = fftBuffer[i * 2 + 1]; spectrum[i] = Math.sqrt(re * re + im * im); } Message msg = mHandler.obtainMessage(MSG_UPDATE_VIEW, spectrum); mHandler.sendMessage(msg); } } }).start(); } private void stopRecording() { mIsRecording = false; mButton.setText(R.string.start_recording); mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; } private void updateView(double[] spectrum) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < spectrum.length; i++) { sb.append(String.format("%d Hz: %.2f\n", i * SAMPLE_RATE / BUFFER_SIZE, spectrum[i])); } mTextView.setText(sb.toString()); } private static class FFT { private int n; private double[] cosTable; private double[] sinTable; FFT(int n) { this.n = n; this.cosTable = new double[n / 2]; this.sinTable = new double[n / 2]; for (int i = 0; i < n / 2; i++) { cosTable[i] = Math.cos(-2 * Math.PI * i / n); sinTable[i] = Math.sin(-2 * Math.PI * i / n); } } void fft(double[] data) { int bits = (int) (Math.log(n) / Math.log(2)); for (int j = 1; j < n / 2; j++) { int swapPos = reverseBits(j, bits); double temp = data[j]; data[j] = data[swapPos]; data[swapPos] = temp; } int size = 2; int halfSize = 1; for (int i = 1; i <= bits; i++) { for (int j = 0; j < n; j += size) { for (int k = 0; k < halfSize; k++) { int evenIndex = j + k; int oddIndex = j + k + halfSize; double even = data[evenIndex]; double odd = data[oddIndex]; double cos = cosTable[k * n / size]; double sin = sinTable[k * n / size]; data[evenIndex] = even + odd * cos - sin * odd; data[oddIndex] = even + odd * cos + sin * odd; } } size *= 2; halfSize *= 2; } } private int reverseBits(int x, int bits) { int y = 0; for (int i = 0; i < bits; i++) { y <<= 1; y |= (x & 1); x >>= 1; } return y; } } } ``` 上面的代码实现了一个简单的录音和频谱图显示功能。当用户点击“开始录音”按钮时,程序会创建一个 `AudioRecord` 对象并开始录音。录音过程中,程序会不断从缓冲区读取音频数据,并计算出其频谱。频谱计算使用了 FFT 算法,该算法可以将时域信号转换为频域信号。最后,程序将计算出的频谱显示在界面上。 需要注意的是,程序中使用了一个自己实现的 FFT 类。如果你对 FFT 算法不熟悉,可以先了解一下 FFT 算法的基本原理,再来理解这段代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值