• webrtc-agc2


    本文主要整理了webrtcagc2模块。目前为止,webrtc提供的agc总共有三个版本,最老的版本在legacy文件夹下,然后就是agc文件下的一个版本,最后一个就是位于agc2文件下的另一版本。相较于之前的版本,agc2引入了RNNvad估计。当然其它的部分也有所改进,如噪声估计、增益求解。webrtcagc2模块打算分两次博文介绍,本篇主要介绍编译以及agc2效果测试,下一篇博文主要介绍自己对agc2算法的理解。agc2的编译所需文件包括:apicommon_audiortc_basesystem_wrappersthird_party以及modules模块下的大部分文件。具体的文件可以参见我的github链接https://github.com/ctwgL/webrtc_agc2。上述文件准备完毕后,编写CMakeLists.txt文件,该部分主要参考https://github.com/lyapple2008/webrtc_apm_cmake

    cmake_minimum_required(VERSION 3.6)
    
    project(webrtc_apm)
    
    set(CMAKE_CXX_STANDARD 14)
    
    if (WIN32)
      set(CMAKE_C_FLAGS "/arch:AVX2")
    else ()
      set(CMAKE_C_FLAGS "-mavx2 -mfma")
    endif()
    
    add_compile_options(-march=native)
    
    if (UNIX)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
    endif()
    
    if (WIN32)
      add_definitions(-DWEBRTC_WIN)
    else()
      add_definitions(-DWEBRTC_POSIX)
    endif ()
    
    if (UNIX)
      add_definitions(-DWEBRTC_LINUX)
    endif ()
    add_definitions(-DWEBRTC_NS_FLOAT)
    add_definitions(-DWEBRTC_APM_DEBUG_DUMP=1)
    
    set(CURRENT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    
    # api
    set(WEBRTC_API_DIR ${CURRENT_DIR}/api)
    set(WEBRTC_API_INCLUDE ${CURRENT_DIR}/api)
    set(WEBRTC_API_AUDIO_DIR ${CURRENT_DIR}/api/audio)
    set(WEBRTC_API_TASK_QUEUE_DIR ${CURRENT_DIR}/api/task_queue)
    
    # common_audio
    set(WEBRTC_COMMON_AUDIO_DIR ${CURRENT_DIR}/common_audio)
    set(WEBRTC_COMMON_AUDIO_INCLUDE ${CURRENT_DIR}/common_audio)
    set(WEBRTC_COMMON_AUDIO_RESAMPLER_DIR ${CURRENT_DIR}/common_audio/resampler)
    set(WEBRTC_COMMON_AUDIO_SIGNAL_PROCESSING_DIR ${CURRENT_DIR}/common_audio/signal_processing)
    set(WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_128_DIR ${CURRENT_DIR}/common_audio/third_party/ooura/fft_size_128)
    set(WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_256_DIR ${CURRENT_DIR}/common_audio/third_party/ooura/fft_size_256)
    set(WEBRTC_COMMON_AUDIO_THIRD_PARTY_SPL_DIR ${CURRENT_DIR}/common_audio/third_party/spl_sqrt_floor)
    set(WEBRTC_COMMON_AUDIO_VAD_DIR ${CURRENT_DIR}/common_audio/vad)
    set(WEBRTC_COMMON_AUDIO_VAD_INCLUDE ${CURRENT_DIR}/common_audio/vad/include)
    
    # modules
    set(WEBRTC_MODULES_AUDIO_CODING_ISAC_VAD_DIR ${CURRENT_DIR}/modules/audio_coding/codecs/isac/main/source)
    # modules->audio_processing
    set(WEBRTC_MODULES_AUDIO_PROCESSING_DIR ${CURRENT_DIR}/modules/audio_processing)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_INCLUDE ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/include)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AEC_DUMP_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/aec_dump)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/aec3)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AECM_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/aecm)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AGC_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/agc)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AGC_LEGACY_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/agc/legacy)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/agc2)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_RNN_VAD_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/agc2/rnn_vad)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/echo_detector)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_LOGGING_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/logging)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_NS_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/ns)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_TRANSIENT_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/transient)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_UTILITY_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/utility)
    set(WEBRTC_MODULES_AUDIO_PROCESSING_VAD_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/vad)
    # modules->third_party->fft
    set(WEBRTC_MODULES_AUDIO_PROCESSING_THIRD_PARTY_FFT_DIR ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR}/third_party/fft)
    
    # rtc_base
    set(WEBRTC_RTC_BASE_DIR ${CURRENT_DIR}/rtc_base)
    set(WEBRTC_RTC_BASE_EXPERIMENTS_DIR ${WEBRTC_RTC_BASE_DIR}/experiments)
    set(WEBRTC_RTC_BASE_MEMORY_DIR ${WEBRTC_RTC_BASE_DIR}/memory)
    set(WEBRTC_RTC_BASE_STRINGS_DIR ${WEBRTC_RTC_BASE_DIR}/strings)
    set(WEBRTC_RTC_BASE_SYNCHRONIZATION_DIR ${WEBRTC_RTC_BASE_DIR}/synchronization)
    set(WEBRTC_RTC_BASE_SYSTEM_DIR ${WEBRTC_RTC_BASE_DIR}/system)
    
    # system_wrappers
    set(WEBRTC_SYSTEM_WRAPPERS_DIR ${CURRENT_DIR}/system_wrappers/source)
    set(WEBRTC_SYSTEM_WRAPPERS_INCLUDE ${CURRENT_DIR}/system_wrappers/include)
    
    # jsoncpp
    set(WEBRTC_THIRD_PARTY_JSONCPP_INCLUDE ${CURRENT_DIR}/third_party/jsoncpp/source/include)
    # pffft
    set(WEBRTC_THIRD_PARTY_PFFFT_INCLUDE ${CURRENT_DIR}/third_party/pffft/src)
    # rnnoise
    set(WEBRTC_THIRD_PARTY_RNNNOISE_DIR ${CURRENT_DIR}/third_party/rnnoise/src)
    
    
    include_directories(
      ${CURRENT_DIR}
      ${WEBRTC_API_INCLUDE}
      ${WEBRTC_COMMON_AUDIO_VAD_INCLUDE}
      ${WEBRTC_COMMON_AUDIO_INCLUDE}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_INCLUDE}
      ${WEBRTC_THIRD_PARTY_JSONCPP_INCLUDE}
      ${WEBRTC_THIRD_PARTY_PFFFT_INCLUDE}
    )
    
    aux_source_directory(${WEBRTC_API_AUDIO_DIR} WEBRTC_API_AUDIO_DIR_SRC)
    
    set(WEBRTC_API_TASK_QUEUE_SRC ${CURRENT_DIR}/api/task_queue/task_queue_base.cc)
    if (WIN32)
    set(WEBRTC_API_DEFAULT_TASK_QUEUE_SRC ${CURRENT_DIR}/api/task_queue/default_task_queue_factory_win.cc)
    elseif (UNIX)
    set(WEBRTC_API_DEFAULT_TASK_QUEUE_SRC ${CURRENT_DIR}/api/task_queue/default_task_queue_factory_stdlib.cc)
    else ()
    set(WEBRTC_API_DEFAULT_TASK_QUEUE_SRC ${CURRENT_DIR}/api/task_queue/default_task_queue_factory_gcd.cc)
    endif()
    
    aux_source_directory(${WEBRTC_COMMON_AUDIO_DIR} WEBRTC_COMMON_AUDIO_DIR_SRC)
    aux_source_directory(${WEBRTC_COMMON_AUDIO_RESAMPLER_DIR} WEBRTC_COMMON_AUDIO_RESAMPLER_DIR_SRC)
    aux_source_directory(${WEBRTC_COMMON_AUDIO_SIGNAL_PROCESSING_DIR} WEBRTC_COMMON_AUDIO_SIGNAL_PROCESSING_DIR_SRC)
    aux_source_directory(${WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_128_DIR} WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_128_DIR_SRC)
    aux_source_directory(${WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_256_DIR} WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_256_DIR_SRC)
    aux_source_directory(${WEBRTC_COMMON_AUDIO_THIRD_PARTY_SPL_DIR} WEBRTC_COMMON_AUDIO_THIRD_PARTY_SPL_DIR_SRC)
    aux_source_directory(${WEBRTC_COMMON_AUDIO_VAD_DIR} WEBRTC_COMMON_AUDIO_VAD_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_CODING_ISAC_VAD_DIR} WEBRTC_MODULES_AUDIO_CODING_ISAC_VAD_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_INCLUDE} WEBRTC_MODULES_AUDIO_PROCESSING_INCLUDE_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AEC_DUMP_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AEC_DUMP_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AECM_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AECM_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AGC_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AGC_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AGC_LEGACY_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AGC_LEGACY_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_RNN_VAD_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_LOGGING_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_LOGGING_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_NS_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_NS_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_TRANSIENT_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_TRANSIENT_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_UTILITY_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_UTILITY_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_VAD_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_VAD_DIR_SRC)
    aux_source_directory(${WEBRTC_MODULES_AUDIO_PROCESSING_THIRD_PARTY_FFT_DIR} WEBRTC_MODULES_AUDIO_PROCESSING_THIRD_PARTY_FFT_DIR_SRC)
    aux_source_directory(${WEBRTC_RTC_BASE_DIR} WEBRTC_RTC_BASE_DIR_SRC)
    aux_source_directory(${WEBRTC_RTC_BASE_EXPERIMENTS_DIR} WEBRTC_RTC_BASE_EXPERIMENTS_DIR_SRC)
    aux_source_directory(${WEBRTC_RTC_BASE_MEMORY_DIR} WEBRTC_RTC_BASE_MEMORY_DIR_SRC)
    aux_source_directory(${WEBRTC_RTC_BASE_STRINGS_DIR} WEBRTC_RTC_BASE_STRINGS_DIR_SRC)
    aux_source_directory(${WEBRTC_RTC_BASE_SYNCHRONIZATION_DIR} WEBRTC_RTC_BASE_SYNCHRONIZATION_DIR_SRC)
    aux_source_directory(${WEBRTC_RTC_BASE_SYSTEM_DIR} WEBRTC_RTC_BASE_SYSTEM_DIR_SRC)
    aux_source_directory(${WEBRTC_SYSTEM_WRAPPERS_DIR} WEBRTC_SYSTEM_WRAPPERS_DIR_SRC)
    aux_source_directory(${WEBRTC_THIRD_PARTY_RNNNOISE_DIR} WEBRTC_THIRD_PARTY_RNNNOISE_DIR_SRC)
    
    add_subdirectory(${CURRENT_DIR}/third_party/abseil-cpp)
    add_subdirectory(${CURRENT_DIR}/third_party/jsoncpp/source)
    add_subdirectory(${CURRENT_DIR}/third_party/pffft/src)
    
    add_library(${PROJECT_NAME} STATIC
      ${WEBRTC_API_AUDIO_DIR_SRC}
      ${WEBRTC_API_TASK_QUEUE_SRC}
      ${WEBRTC_API_DEFAULT_TASK_QUEUE_SRC}
      ${WEBRTC_COMMON_AUDIO_DIR_SRC}
      ${WEBRTC_COMMON_AUDIO_RESAMPLER_DIR_SRC}
      ${WEBRTC_COMMON_AUDIO_SIGNAL_PROCESSING_DIR_SRC}
      ${WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_128_DIR_SRC}
      ${WEBRTC_COMMON_AUDIO_THIRD_PARTY_OOURA_256_DIR_SRC}
      ${WEBRTC_COMMON_AUDIO_THIRD_PARTY_SPL_DIR_SRC}
      ${WEBRTC_COMMON_AUDIO_VAD_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_CODING_ISAC_VAD_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_INCLUDE_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AEC_DUMP_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AECM_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AGC_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AGC_LEGACY_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_AGC2_RNN_VAD_DIR}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_LOGGING_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_NS_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_TRANSIENT_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_UTILITY_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_VAD_DIR_SRC}
      ${WEBRTC_MODULES_AUDIO_PROCESSING_THIRD_PARTY_FFT_DIR_SRC}
      ${WEBRTC_RTC_BASE_DIR_SRC}
      ${WEBRTC_RTC_BASE_EXPERIMENTS_DIR_SRC}
      ${WEBRTC_RTC_BASE_MEMORY_DIR_SRC}
      ${WEBRTC_RTC_BASE_STRINGS_DIR_SRC}
      ${WEBRTC_RTC_BASE_SYNCHRONIZATION_DIR_SRC}
      ${WEBRTC_RTC_BASE_SYSTEM_DIR_SRC}
      ${WEBRTC_SYSTEM_WRAPPERS_DIR_SRC}
      ${WEBRTC_THIRD_PARTY_RNNNOISE_DIR_SRC}
    )
    target_link_libraries(${PROJECT_NAME} absl::strings absl::optional absl::base jsoncpp_static pffft)
    

    另外还需准备agc2的测试demo,该demo只是基于我对webrtc代码的理解,也有可能理解不对,因为在测试过程中,效果并没有期望的那么好,所以分享出来这个项目希望大佬能够指正问题

    #include "modules/audio_processing/gain_control_impl.h"
    #include "modules/audio_processing/gain_controller2.h"
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include "modules/audio_processing/audio_buffer.h"
    #include "common_audio/wav_file.h"
    #include "common_audio/wav_header.h"
    #include "common_audio/channel_buffer.h"
    #include "common_audio/include/audio_util.h"
    #include "common_audio/test_utils.h"
    
    using namespace std;
    using namespace webrtc;
    
    const int kChunkSizeMs = 10;
    const int kSampleRate16kHz = 16000;
    
    struct Agcinput{
        char* input_file;
        char* output_file;
    };
    
    void agc2(struct Agcinput* agc_input){
        std::unique_ptr<WavReader> in_file(new WavReader(agc_input->input_file));
        int input_sample_rate_hz = in_file->sample_rate();
        int input_num_channels = in_file->num_channels();
    
        std::unique_ptr<WavWriter> out_file(new WavWriter(agc_input->output_file,input_sample_rate_hz,input_num_channels));
        std::unique_ptr<ChannelBufferWavReader> buffer_reader_;
        buffer_reader_.reset(new ChannelBufferWavReader(std::move(in_file)));
    
        std::unique_ptr<ChannelBuffer<float>> in_buf_;
        int kChunksPerSecond = 1000 / 10;
        in_buf_.reset(new ChannelBuffer<float>(input_sample_rate_hz / kChunksPerSecond,input_num_channels));
    
        std::unique_ptr<ChannelBufferWavWriter> buffer_writer_;
        buffer_writer_.reset(new ChannelBufferWavWriter(std::move(out_file)));
        std::unique_ptr<ChannelBuffer<float>> out_buf_;
        out_buf_.reset(new ChannelBuffer<float>(input_sample_rate_hz / kChunksPerSecond,input_num_channels));
    
        AudioProcessing::Config::GainController2 agc2_config;
        agc2_config.enabled=true;
        agc2_config.adaptive_digital.enabled=true;
        agc2_config.fixed_digital.gain_db=5;
    
        std::unique_ptr<GainController2> gainController2;
        gainController2.reset(new GainController2);
        gainController2->Initialize(input_sample_rate_hz);
        gainController2->ApplyConfig(agc2_config);
        RTC_CHECK_EQ(gainController2->Validate(agc2_config), true);
        StreamConfig sc(input_sample_rate_hz,input_num_channels);
        AudioBuffer ab(input_sample_rate_hz / kChunksPerSecond,input_num_channels,input_sample_rate_hz / kChunksPerSecond,input_num_channels,input_sample_rate_hz / kChunksPerSecond);
    
        bool samples_left_process = true;
        int count = 0;
        while (samples_left_process){
            samples_left_process = buffer_reader_->Read(in_buf_.get());
            ab.CopyFrom(in_buf_->channels(), sc);
            if(input_sample_rate_hz > kSampleRate16kHz){
                ab.SplitIntoFrequencyBands();
            }
            gainController2->NotifyAnalogLevel(5);
            gainController2->Process(&ab);
    
            if(input_sample_rate_hz > kSampleRate16kHz){
                ab.MergeFrequencyBands();
            }
            ab.CopyTo(sc, out_buf_->channels());
            buffer_writer_->Write(*out_buf_);
            count++;
        }
    }
    
    int main(int argc, char* argv[]){
        std::cout << "webrtc audio processing agc2 test" << std::endl;
        char* input_file = argv[1];
        char* output_file = argv[2];
    
        Agcinput agc_handle;
        agc_handle.input_file = input_file;
        agc_handle.output_file = output_file;
        agc2(&agc_handle);
        return 0;
    }
    

    该部分的CMakeLists.txt文件如下

    cmake_minimum_required(VERSION 3.6)
    
    project(test_apm)
    
    set(CMAKE_CXX_STANDARD 14)
    
    set(CURRENT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    
    include_directories(${CURRENT_DIR}/../webrtc)
    
    add_executable(${PROJECT_NAME}
      ${CURRENT_DIR}/main.cc
    )
    target_link_libraries(${PROJECT_NAME} webrtc_apm)
    

    最后建立整个工程的CMakeLists.txt文件

    cmake_minimum_required(VERSION 3.6)
    
    project(webrtc_apm_cmake)
    
    set(CURRENT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    
    add_subdirectory(${CURRENT_DIR}/webrtc)
    add_subdirectory(${CURRENT_DIR}/test_apm)
    

    整个工程的项目列表如下

    执行如下代码

    cmake .
    make
    

    即可得到可执行文件,然后在Clion中配置输入参数得到运行结果如下。(注:该demo只适用于采样率16k, 单通道的wav文件)

    增强后的语音如下

    另外在测试的过程中发现对于语音能量较小的语音文件,会出现延迟方法的现象。以及对于语音中有大小声的情况,自适应的效果也不是很明显。

  • 相关阅读:
    Masterha-manager避免自动关闭的方法
    MHA自动切换流程
    vue 使用keep-alive缓存tab切换组件,保持每个组件滚动条位置
    el-select 输入下拉搜索,匹配不到数据时也保留输入值,同时input获取焦点时保留其value值
    尝试 React16、React-router4 实现根据动态菜单生成按需加载的路由
    vue iscroll5滚动条组件
    vue项目中 axios请求拦截器与取消pending请求功能
    jquery编写的简单日历
    手机访问电脑wampServer本地环境页面
    ajax在ie下返回未定义解决方案
  • 原文地址:https://www.cnblogs.com/tingweichen/p/14094825.html
Copyright © 2020-2023  润新知