前言

广义的音频重采样包括:
1、采样格式转化:比如采样格式从16位整形变为浮点型
2、采样率的转换:降采样和升采样,比如44100采样率降为2000采样率
3、存放方式转化:音频数据从packet方式变为planner方式。有的硬件平台在播放声音时需要的音频数据是planner格式的,而有的可能又是packet格式的,或者其它需求原因经常需要进行这种存放方式的转化

通常意义上的音频重采样是指上述的第2点,即升采样和降采样,这个对音质有一定影响

重采样相关函数流程图

1584929874392.jpg

重采样ffmpeg相关函数介绍

这些重采样函数都位于头文件<libswresampe/swresample.h>

  • 1、SwrContext
    实现重采样的上下文,它是一个结构体,具体定义未知
  • 2、SwrContext *swr_alloc();
    分配上下对象
  • 3、
    int av_opt_set_int(void *obj,char name,int64_t val,int search_flags);
    int av_opt_set_sample_fmt(void
    obj,char *name,enum AVSampleFormat fmt,int search_flags);
    给上下文对象设置重采样的转换参数
  • 4、int swr_init(struct SwrContext * swrCtx);
    设置参数之后初始化重采样上下文
  • 5、struct SwrContext* swr_alloc_set_opts(struct SwrContext *swr_ctx,
    int64_t out_ch_layout,enum AVSampleFormat out_sample_fmt,int out_sample_rate,
    int64_t in_ch_layout,enum AVSampleFormat in_ch_layout,int in_sample_rate
    );
    此函数功能相当于上面1、2、3、4所有函数的功能
  • 6、int swr_convert(struct SwrContext swrCtx,uint8_t* out,int out_count,
    const uint8_t **int,int in_count)
    进行格式转换,返回值为实际转换的采样数,该值小于等于out_count
  • 7、int swr_convert_frame(struct SwrContext *swrCtx,AVFrame **outframe,AVFrame inframe);
    返回值为0代表转换成功。当进行升采/降样的时候,内部缓冲中会包含部分数据,要想全部获取这些数据,inframe要传递NULL
  • 8、void swr_free(struct SwrContext **s);
    释放上下文

ffmpeg实现音频重采样

ffmpeg提供了实现广义重采样的方式,通过libswresample库实现,可以通过具体实现代码如下:
头文件

#ifndef audio_resample_hpp
#define audio_resample_hpp#include <stdio.h>
#include <string>
#include "cppcommon/CLog.h"using namespace std;extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}/** 广义的音频的重采样包括:*  1、采样格式转化:比如采样格式从16位整形变为浮点型*  2、采样率的转换:降采样和升采样,比如44100采样率降为2000采样率*  3、存放方式转化:音频数据从packet方式变为planner方式。有的硬件平台在播放声音时需要的音频数据是*  planner格式的,而有的可能又是packet格式的,或者其它需求原因经常需要进行这种存放方式的转化*/
class AudioResample
{
public:AudioResample();~AudioResample();void doResample();void doResampleAVFrame();
private:
};#endif /* audio_resample_hpp */

上面doResample()对应swr_convert()函数,doResampleAVFrame()函数对应
swr_convert_frame()函数

  • 具体实现:
/** av_frame_get_buffer()给AVFrame分配的音频内存和av_samples_alloc_array_and_samples()分配的音频内存块的区别*  1、前者内存由引用计数管理,而且对于planner方式音频,是两个独立的内存块,内存块首地址不一样;*  2、后者内存和普通的内存管理方式一样,需要自己手动释放,对于planner方式音频,它也是一个连续的内存块,只不过每个planner对应的首地址分别存储于*  指向的数组指针中;释放由函数av_freep(dataAddr),dataAddr指的连续内存块的首地址*/
/** 重采样实验:*  1、44100的采样率,升采样48000或者降采样24000 后对音质影响不大,存储到文件中的数值会变化*  2、float类型的采样数据,转换成32位整数型采样数据 对音质影响也不大,存储到文件中的数值会变化*/
void AudioResample::doResample()
{string curFile(__FILE__);unsigned long pos = curFile.find("ffmpeg-demo");if (pos == string::npos) {  // 未找到return;}string resouceDic = curFile.substr(0,pos)+"ffmpeg-demo/filesources/";// 原pcm文件中存储的是float类型音频数据,小端序;packet方式string pcmpath = resouceDic+"test_441_f32le_2.pcm";string dstpath1 = "test_240_s32le_2.pcm";FILE *srcFile = fopen(pcmpath.c_str(), "rb+");if (srcFile == NULL) {LOGD("fopen srcFile fail");return;}FILE *dstFile = fopen(dstpath1.c_str(), "wb+");if (dstFile == NULL) {LOGD("fopen dstFile fail");return;}// 音频数据uint8_t **src_data,**dst_data;// 声道数int src_nb_channels=0,dst_nb_channels=0;int64_t src_ch_layout = AV_CH_LAYOUT_STEREO,dst_ch_layout = AV_CH_LAYOUT_STEREO;// 采样率int64_t src_rate = 44100,dst_rate = 48000;// 采样格式和音频数据存储方式const enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT,dst_sample_fmt = AV_SAMPLE_FMT_S32P;int src_nb_samples=1024,dst_nb_samples,max_dst_nb_samples;int src_linesize,dst_linesize;// 1、创建上下文SwrContextSwrContext *swrCtx;swrCtx = swr_alloc();if (swrCtx == NULL) {LOGD("swr_allock fail");return;}// 2、设置重采样的相关参数 这些函数位于头文件 <libavutil/opt.h>av_opt_set_int(swrCtx, "in_channel_layout", src_ch_layout, 0);av_opt_set_int(swrCtx, "in_sample_rate", src_rate, 0);av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", src_sample_fmt, 0);av_opt_set_int(swrCtx, "out_channel_layout", dst_ch_layout, 0);av_opt_set_int(swrCtx, "out_sample_rate", dst_rate, 0);av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", dst_sample_fmt, 0);// 3、初始化上下文int ret = 0;ret = swr_init(swrCtx);if (ret < 0) {LOGD("swr_init fail");return;}src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);// 根据src_sample_fmt、src_nb_samples、src_nb_channels为src_data分配内存空间,和设置对应的的linesize的值;返回分配的总内存的大小int src_buf_size = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels, src_nb_samples, src_sample_fmt, 0);// 根据src_nb_samples*dst_rate/src_rate公式初步估算重采样后音频的nb_samples大小max_dst_nb_samples = dst_nb_samples = (int)av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);int dst_buf_size = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);size_t read_size = 0;while ((read_size = fread(src_data[0], 1, src_buf_size, srcFile)) > 0) {/** 因为有转换时有缓存,所以要不停的调整转换后的内存的大小。估算重采样后的的nb_samples的大小,这里swr_get_delay()用于获取重采样的缓冲延迟*  dst_nb_samples的值会经过多次调整后区域稳定*/dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(swrCtx, src_rate)+src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);if (dst_nb_samples > max_dst_nb_samples) {LOGD("要重新分配内存了");// 先释放以前的内存,不管sample_fmt是planner还是packet方式,av_samples_alloc_array_and_samples()函数都是分配的一整块连续的内存av_freep(&dst_data[0]);// 再重新分配内存dst_buf_size = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);max_dst_nb_samples = dst_nb_samples;}// 开始重采样,重采样后的数据将根据前面指定的存储方式写入ds_data内存块中,返回每个声道实际的采样数/***  遇到问题:转换后声音不对*  原因分析:swr_convert()函数返回的result是实际转换的采样个数,该值肯定小于等于预计采样数dst_nb_samples,所以写入文件的时候不能用dst_nb_samples的*  值,而应该用result值**/int result = swr_convert(swrCtx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);if (result < 0) {LOGD("swr_convert fail %d",result);break;}LOGD("read_size %d dst_nb_samples %d src_nb_samples %d result %d",read_size,dst_nb_samples,src_nb_samples,result);// 将音频数据写入pcm文件if (av_sample_fmt_is_planar(dst_sample_fmt)) {  // planner方式,而pcm文件写入时一般都是packet方式,所以这里要注意转换一下int size = av_get_bytes_per_sample(dst_sample_fmt);// 这里必须是result,而不能用dst_nb_samples,因为result才代表此次实际转换的采样数 它肯定小于等于dst_nb_samplesfor (int i=0; i<result;i++) {for (int j=0; j<dst_nb_channels; j++) {fwrite(dst_data[j]+i*size, 1, size, dstFile);}}} else {// 最后一个参数必须为1 否则会因为cpu对齐算出来的大小大于实际的数据大小 导致多写入数据 从而造成错误dst_buf_size = av_samples_get_buffer_size(&dst_linesize,dst_nb_channels, result,dst_sample_fmt,1);fwrite(dst_data[0], 1, dst_buf_size, dstFile);}}// 还有剩余的缓存数据没有转换,第三个传递NULL,第四个传递0即可将缓存中的全部取出do {int real_nb_samples = swr_convert(swrCtx,dst_data,dst_nb_samples,NULL,0);if (real_nb_samples <=0) {break;}LOGD("余量 %d",real_nb_samples);if (av_sample_fmt_is_planar(dst_sample_fmt)) {int size = av_get_bytes_per_sample(dst_sample_fmt);for (int i=0; i<real_nb_samples; i++) {for (int j=0; j<dst_nb_channels; j++) {fwrite(dst_data[j]+i*size,1,size,dstFile);}}} else {int size = av_samples_get_buffer_size(NULL,dst_nb_channels,real_nb_samples,dst_sample_fmt,1);fwrite(dst_data[0], 1, size, dstFile);}} while (true);// 释放资源av_freep(&src_data[0]);av_freep(&dst_data[0]);swr_free(&swrCtx);fclose(srcFile);fclose(dstFile);
}
  • 代码解读
    1、要进行转换的PCM数据位于项目同级目录下的filesources文件夹中,这里采用的是test_441_f32le_2.pcm,代表采样格式float类型小端序,采样率44100 声道数2的数据
    2、src_channel_layout,src_sample_rate,src_sample_fmt三个变量要与步骤1中的对应起来。dst_channel_layout,dst_sample_rate,dst_sample_fmt则可以按照自己的需求进行转换
    3、代码 dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(swrCtx, src_rate)+src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
    为不停调整转后后的预计目标采样数大小,此代码可有可无,不是必须的
    4、swr_convert()函数返回的是实际转换的目标采样数大小,所以dst_data中数据的大小应该是该函数返回的值,而非前面通过dst_nb_samples计算出来的值
    5、转换过程中可能会有部分为转换的数据存在于内部缓冲中,通过int real_nb_samples = swr_convert(swrCtx,dst_data,dst_nb_samples,NULL,0);
    代码将这些剩余的数据转换完毕
void AudioResample::doResampleAVFrame()
{string curFile(__FILE__);unsigned long pos = curFile.find("ffmpeg-demo");if (pos == string::npos){LOGD("cannot find file");return;}string srcpcm = curFile.substr(0,pos)+"ffmpeg-demo/filesources/test_441_f32le_2.pcm";string dstpcm = "test_441_s32le_2.pcm";uint64_t src_channel_layout = AV_CH_LAYOUT_STEREO,dst_channel_layout = AV_CH_LAYOUT_STEREO;int src_nb_channels = av_get_channel_layout_nb_channels(src_channel_layout);int dst_nb_channels = av_get_channel_layout_nb_channels(dst_channel_layout);int64_t src_nb_samples = 1024,dst_nb_samples,max_dst_nb_samples;int64_t src_sample_rate = 44100,dst_sample_rate=44100;const enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT,dst_sample_fmt = AV_SAMPLE_FMT_S32P;FILE *srcFile,*dstFile;if ((srcFile = fopen(srcpcm.c_str(), "rb")) == NULL) {LOGD("open srcpcm fail");return;}if ((dstFile = fopen(dstpcm.c_str(),"wb+")) == NULL) {LOGD("open dstpcm fail");return;}AVFrame *srcFrame,*dstFrame;srcFrame = av_frame_alloc();srcFrame->nb_samples = (int)src_nb_samples;srcFrame->format = src_sample_fmt;srcFrame->sample_rate = (int)src_sample_rate;srcFrame->channel_layout = src_channel_layout;srcFrame->channels = src_nb_channels;int ret = av_frame_get_buffer(srcFrame,0);if (ret < 0) {LOGD("av_frame_get_buffer() return faile %d",ret);return;}ret = av_frame_make_writable(srcFrame);if (ret < 0) {LOGD("av_frame_make_writable() fail %d",ret);return;}// 采样数跟采样率有关 目标采样数根据由采样率根据比例公式计算出// R2 = R1*(β2/β1) R1 称作参考源 与R2是同一类型数据,β1、β2称作比例因子,影响系数。R1,β2,β1一次对应这个公式的第一二三个系数max_dst_nb_samples = dst_nb_samples = (int)av_rescale_rnd(src_nb_samples,dst_sample_rate,src_sample_rate,AV_ROUND_UP);dstFrame = av_frame_alloc();dstFrame->sample_rate = (int)dst_sample_rate;dstFrame->format = dst_sample_fmt;dstFrame->nb_samples = (int)dst_nb_samples;dstFrame->channel_layout = dst_channel_layout;dstFrame->channels = dst_nb_channels;ret = av_frame_get_buffer(dstFrame,0);if(ret < 0) {LOGD("av_frame_get_buffer2 failt %d",ret);return;}ret = av_frame_make_writable(dstFrame);if (ret < 0){LOGD("av_frame_make_writable() 2 failt %d",ret);return;}// 1、创建上下文SwrContext *swr_ctx = swr_alloc();if (swr_ctx == NULL) {LOGD("swr_alloc fail");return;}// 2、设置转换参数 在<libswresample/options.c>文件中可以找到定义av_opt_set_int(swr_ctx,"in_channel_layout",src_channel_layout,AV_OPT_SEARCH_CHILDREN);av_opt_set_int(swr_ctx,"in_sample_rate",src_sample_rate,AV_OPT_SEARCH_CHILDREN);av_opt_set_sample_fmt(swr_ctx,"in_sample_fmt",src_sample_fmt,AV_OPT_SEARCH_CHILDREN);av_opt_set_int(swr_ctx,"out_channel_layout",dst_channel_layout,AV_OPT_SEARCH_CHILDREN);av_opt_set_int(swr_ctx,"out_sample_rate",dst_sample_rate,AV_OPT_SEARCH_CHILDREN);av_opt_set_sample_fmt(swr_ctx,"out_sample_fmt",dst_sample_fmt,AV_OPT_SEARCH_CHILDREN);// 3、执行初始化ret = swr_init(swr_ctx);if (ret < 0) {LOGD("swr_init fail");return;}int require_size = av_samples_get_buffer_size(NULL,src_nb_channels,(int)src_nb_samples,src_sample_fmt,0);size_t read_size = 0;// 这里必须传递srcFrame->data[0]这个地址,而不能传递srcFrame->datawhile ((read_size = fread(srcFrame->data[0],1,require_size,srcFile)) > 0) {/** 当转换后的采样率大于转换前的转换率时,内部会有缓冲,所以预计转换后的目标采样数会一直变化 所以这里目标音频内存大小也要跟着动态调整;当然也可以不做调整*/dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx,src_sample_rate)+src_nb_samples,dst_sample_rate,src_sample_rate,AV_ROUND_UP);if (dst_nb_samples > max_dst_nb_samples) {max_dst_nb_samples = dst_nb_samples;av_frame_unref(dstFrame);LOGD("重新调整转换后存储音频数据内存大小 %d",max_dst_nb_samples);AVFrame *tmp = av_frame_alloc();tmp->format = dst_sample_fmt;tmp->nb_samples = (int)dst_nb_samples;tmp->channel_layout = dst_channel_layout;tmp->channels = dst_nb_channels;tmp->sample_rate = (int)dst_sample_rate;ret = av_frame_get_buffer(tmp,0);if (ret < 0) {LOGD("av_frame_get_buffer fail %d",ret);break;}ret = av_frame_make_writable(tmp);if (ret < 0) {LOGD("av_frame_make_writable()11 fail %d",ret);break;}av_frame_move_ref(dstFrame, tmp);}// 4、音频格式转换 返回实际转换的采样数 <= dst_nb_samples// 对于音频 AVFrame中extended_data和data属性指向同一块内存int real_convert_nb_samples;
#define UseAVFrame
#ifndef UseAVFrameconst uint8_t **srcdata = (const uint8_t **)srcFrame->extended_data;uint8_t **outdata = dstFrame->extended_data;real_convert_nb_samples = swr_convert(swr_ctx,outdata,(int)dst_nb_samples,srcdata,(int)src_nb_samples);LOGD("swr_convert nb_samples %d",real_convert_nb_samples);
#elseret = swr_convert_frame(swr_ctx,dstFrame,srcFrame);if (ret < 0) {LOGD("swr_convert_frame fail %d",ret);continue;}real_convert_nb_samples = dstFrame->nb_samples;LOGD("swr_convert_frame nb_samples %d",real_convert_nb_samples);
#endif// 5、存储;由于文件是planner格式,所以对于planner格式的存储要注意下if (av_sample_fmt_is_planar(dst_sample_fmt)) {int size = av_get_bytes_per_sample(dst_sample_fmt);for (int i =0; i< real_convert_nb_samples;i++) {for (int j=0;j<dst_nb_channels;j++) {fwrite(dstFrame->data[j]+i*size,1,size,dstFile);}}} else {int size = av_samples_get_buffer_size(NULL,dst_nb_channels,real_convert_nb_samples,dst_sample_fmt,1);fwrite(dstFrame->data[0],1,size,dstFile);}}/** 当转换后的采样率大于转换前的转换率时,内部会有缓冲*/
#ifdef UseAVFrame// 会有内部缓冲,这里传递NULL,将剩余的数据全部读取出来while (swr_convert_frame(swr_ctx,dstFrame,NULL) >= 0) {if (dstFrame->nb_samples <= 0) {break;}LOGD("还有余量 %d",dstFrame->nb_samples);
#elseuint8_t **outdata = dstFrame->extended_data;int real_convert_nb_samples = 1;while (real_convert_nb_samples > 0) {// 会有内部缓冲,这里第三个传递NULL,第四个传递0即可。将剩余的数据全部读取出来real_convert_nb_samples = swr_convert(swr_ctx,outdata,(int)dst_nb_samples,NULL,0);LOGD("还有余量 %d",real_convert_nb_samples);
#endifif (av_sample_fmt_is_planar(dst_sample_fmt)) {int size = av_get_bytes_per_sample((dst_sample_fmt));for (int i=0; i<dstFrame->nb_samples; i++) {for (int j=0; j<dstFrame->channels; j++) {fwrite(dstFrame->data[j]+i*size,1,size,dstFile);}}} else {int size = av_samples_get_buffer_size(NULL,dstFrame->channels,dstFrame->nb_samples,(enum AVSampleFormat)dstFrame->format,1);fwrite(dstFrame->data[0],1,size,dstFile);}}swr_free(&swr_ctx);av_frame_unref(srcFrame);av_frame_unref(dstFrame);fclose(srcFile);fclose(dstFile);
}

遇到问题

1、转换后声音不对
原因分析:
swr_convert()函数返回的result是实际转换的采样个数,该值肯定小于等于预计采样数dst_nb_samples,所以写入文件的时候不能用dst_nb_samples的值,而应该用result值
具体参考代码

项目地址

项目地址

代码位于cppsrc目录下
audio_resample.hpp
audio_resample.cpp

项目下示例可运行于iOS/android/mac平台,工程分别位于demo-ios/demo-android/demo-mac三个目录下,可根据需要选择不同平台

音频重采样ffmpeg(九)相关推荐

  1. FFmpeg系列(五)—— 音频重采样

    文章目录 为什么要重采样 完整代码 运行结果 为什么要重采样 从设备采集的音频数据与编码器要求的数据不一致 扬声器要求的音频数据与要播放的音频数据不一致 更方便运算(回音消除须使用单声道,需要先转换) ...

  2. 音视频从入门到精通——FFmpeg之swr_convert音频重采样函数分析

    文章目录 音频重采样 swr_alloc函数 swr_alloc_set_opts函数 swr_init函数 swr_convert函数 音频基础 音频开发主要应用有 音频开发具体内容有 音频应用的难 ...

  3. 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

    文章目录 I . FFMPEG 播放视频流程 II . FFMPEG 音频重采样流程 III . FFMPEG 音频重采样 IV . FFMPEG 初始化音频重采样上下文 SwrContext V . ...

  4. 【ffmpeg】音频重采样

    [ffmpeg]音频重采样 前言 什么是音频重采样 实现音频重采样 创建重采样上下文 初始化重采样 进行重采样 ffplay播放 参考资料 个人简介

  5. ffmpeg音频重采样

    音频重采样 重采样就是改变音频的采样率.sample format(采样格式).声道数(channel)等参数 为什么需要重采样? 1.音频重采样是为了将音频统一格式 比如直播: 麦克风采集 44.1 ...

  6. 音频处理基本概念及音频重采样

    音频处理基本概念及音频重采样 目录 重点问题 重采样 什么是重采样 为什么要重采样 可调节的参数 对应参数解析 采样率 采样格式及量化精度(位宽) 分⽚(plane)和打包(packed) 声道分布( ...

  7. swr_convert音频重采样介绍

    在做音频处理的时候,我们有时候需要调整音频流的采样率 或者 采样格式,可能是喇叭不支持 48000 采样率,所以需要降低到 44100 采样了.也可能因为各种业务原因,需要调整 采样率,采样格式,或者 ...

  8. 关于PCM音频重采样思路及注意事项(频率变换和通道数变换(单通道转双通道))

    最近在做一个语音广播的项目,实现语音广播的过程主要是:音频采集.音频编码.音频发送.音频解码和音频播放,这个过程在这里不展开说明,本文主要讲述其中的音频重采样和音频降噪的问题和记录如何解决的思路.本文 ...

  9. 【QT项目——视频播放器——解码】5.1decoder-5.10音频重采样

    5.1 avcodec_find_decoder.AVCodecContext.avcodec_parameters_to_context 1.确定解码器,通过avcodec_find_decoder ...

最新文章

  1. Docker是世界上最牛逼的CaaS!
  2. 由于SSH配置文件的不匹配,导致的Permission denied (publickey)及其解决方法
  3. dfs-Rank the Languages
  4. 设计模式-Adapter模式
  5. 解决:build_attrs() takes at most 2 arguments (3 given)
  6. 小汤学编程之JAVA基础day13——I/O流
  7. 复盘SpringBoot中定时任务和异步线程池
  8. ehcache缓存原理_Mybatis-09-缓存
  9. php 查询access数据库操作,php操作access数据库的方法详解
  10. 2018 Google IO大会来了
  11. thinkphp的column()函数
  12. python批量修改照片的大小
  13. 如何解决企业税务压力?这些企业税收优惠政策您要了解
  14. MapReduce处理两个文件内不同行信息
  15. 失踪61年的上帝之鸟重回美国阿肯色州 (组图)
  16. 说实话,Jenkins 真得牛逼!只是你不会用而已~
  17. 青帮大佬杜月笙的另一面及其后代现状
  18. R语言中apply函数族的介绍
  19. 车队车载调频广播电台移动发射系统的设计与应用
  20. 服务器网站显示1004报错,失败:错误域= NSURLErrorDomain代码= -1004“无法连接到服务器。”...

热门文章

  1. AVFoundation音视频精准截取(九)
  2. mcnpf5输出结果_MCNP入门教程
  3. 美通企业日报 | 阿迪达斯和碧昂丝达成标志性合作;万豪国际公布亚太区2020年发展愿景...
  4. 文件系统(十一)—fuse内核实现
  5. BeautifulSoup中find和find_all的使用
  6. find和find_if
  7. 教你秒建受信任的本地 SSL 证书,彻底解决开发测试环境的无效证书警告烦恼!...
  8. Oracle 存储过程 问题记录
  9. PyTorch 深度剖析:并行训练的 DP 和 DDP 分别在什么情况下使用及实例
  10. setInterval定时器封装(settimeout\requestAnimationFrame),代码抽离,浏览器切换后定时器停止执行