音视频开发:音频编码原理+采集+编码实战

原理:

  1. 消除冗余信息,压缩量最大,也叫有损压缩
  • 剔除人耳听觉范围外的音频信号20Hz以下和20000Hz以上;
  • 去除被掩蔽的音频信号,信号的遮蔽可以分为频域遮蔽和时域遮蔽;
  • 频域遮蔽效应
    屏蔽70分贝以下,20HZ以下,20000HZ以上
    屏蔽分贝小,频率小的声音
    两个频率相近发出的声音,去除低强度的,也就是分贝高的会盖住分贝低的

  • 时域遮蔽效应:
    根根时间推移,相近频率且同时出现的声音,声音强度高的遮蔽强度低的声音,并且去除同一时间段前后杂音,前遮蔽50毫秒,后遮蔽200毫秒,在这段时间内的声音,强度越接近就越会被屏蔽。

  1. 去除冗余信息后,再进行无损压缩;
  • 无损压缩就是压缩后的数据能够解压缩进行还原,有损则不能;
  • 熵编码中有
    哈夫曼编码:用一个很小的二进制数代替一个长的字符串,频率越高,编码越小,频率越低,编码越长
    算术编码:利用小数进行编码,在香农编码的基础改进而来的
    香农编码

音频编码过程

数据先同时通过 时域转频域变换器和心理学模型处理数据,前者将数据转换成多种频段的数据,然后剔除不需要的频段数据,后者会去除非人耳听到的范围声音和一些复合声音,最后将两者合并经过量化编码,无损编码之类的,形成比特流数据,在此之前还会有一些辅助数据,此后数据就会变得非常小;

常见的音频编码器

opus、aac、Ogg、Speex、iLBC、AMR、G.711, 最常用的编码器是opus aac。
opus常用于直播,尤其是无延迟的直播,webrtc默认使用opus;
AAC是应用最广泛的编解码;
Ogg收费;
Speex支持回音消除;
G.711一般用于固定电话,声音损耗严重,通话会失真;

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~

 

AAC比较适合有一定延迟的直播,AAC-LD属于低延迟编码器

  • AAC编码器:目前应用最广泛,如iOS、安卓和其他嵌入式设备都包含了AAC硬件编解码器,主要学习这个编码器;
    用来取代mp3,比mp3更高的压缩比和保真性更强;



常用的规格有AAC LC、AAC HE V1 、AAC HE V2三种;

AAC HE V1 = AAC + SBR;
AAV HE V2 = AAC + SBR + PS;
目前AAC HE V1 已经被取代 V2 取代了;

V2的码流跟V1的差别不是很大,根据声音的数据变化,如果两个声道的差别很大,码流差别就会越小;

AAC 中header有两种格式:

就相当于在aac数据前面加了个Header,header里面就会包含aac数据的一些信息,方便进行编解码

  1. ADIF(Audio data interchange format): 特点是只能从头开始解码,可以确定的找到音频数据的开始部分,不能从音频数据中间开始,这种格式常用于磁盘文件中;
  2. ADTS(Audio Data Transport Format):在每一帧的数据里面都会有一个同步字,也就是每帧都有一个header,所以他可以在任意的位置开始进行解码,就像流式数据;
  • ADTS结构: 由7-9个字节组成,通常情况下是7个字节,如果有CRC 就是9个字节,字节中的每一位都有独特的含义;
    • 1~12bit:全部是1也就是0xFFF,表示是同步字;
    • 13:编码规范 0 = MPEG-4 1 = MPEG-2;
    • 14~15:总是0;
    • 16:是否有保护 1 代表 没有 CRC 0 代表有CRC;
    • 17~18:表示的是MPEG-4的音频类型:AAC LC、 AAC HE V1 、AAC HE V2
    • 19~22:表示的是采样率
    • 24~26:通道数
    • 31~33:数据长度,也包括了header的长度
  • 剩余的之后补上



其中每一十进制数对应的含义:

Audio Object Type: 在代码中实际获取类型的时候需要进行+1,才是下面的类型
1 == AAC main
2 == AAC LC
5 == SBR == HE V1
29 == ps == HE V2

其中的采样率是通过十进制数表示的一个采样率,有一个表,比如:0 == 96000Hz 1 == 88200HZ 等

音频采集实战

每个端音频采集的底层和应用层的库是不一样的,所以使用ffmpeg中间层能够实现跨平台开发;

  • Android端的底层库是AudioRecorder,应用层是MediaRecorder;
  • iOS端的底层库是AudioUnit,应用层是AVFoundation;
  • Windows端的常用的是Directshow OpenAL 还有Windows7之上的AudioCore;

使用ffmpeg有两种采集方式:

  1. 使用命令方式,命令详情查看ffmpeg相关指令的那篇
  2. 使用代码调用api的方式
  • 在mac下的动态库需要对动态库进行签名

获取本地签名证书列表:/usr/bin/security find-identity -v -p codesigning
查看动态库是否签名: codesign -d -vv 动态库文件
签名命令:codesign -fs "iPhone Distribution: 你的签名证书." 动态库文件
xcode环境:13.2.1
签名了如果还是报错,关掉沙盒并且设置 Enable Hardened Runtime 为NO
在项目中设置user header search path的时候,要使用全路径方式,我使用$(PROJECT_NAME)方式,有的头文件在链接的时候会报错;

采集音频的步骤:

  1. 打开输入输出设备,涉及的包是avdevice avformat 注册设备 设置采集方式,根据平台选择,即设置输入 打开音频设备
  2. 获取数据包 包:avcodec 主要使用av_read_frame方法获取数据 将数据放入packet中 在读取的时候注意缓冲区无未准备好的情况
  3. 将数据输出到文件 创建文件--- fopen 将数据写入文件-- fwrite 关闭文件 -- fclose
  • 打开设备 ·
void startRecorder(void) {// 上下文AVFormatContext *av_context = NULL;AVDictionary *options = NULL;// 1. 注册设备avdevice_register_all();// 2. 设置采集方式//设置采集方式 mac os 下是AVfoundation Windows下是dshow  linux 下是alsaAVInputFormat *format = av_find_input_format("avfoundation");// 3. 打开设备//里面的识别格式为[[video device]:[audio device]]  这里写0 是获取第1个音频设备char *name = ":0";// url 是路径 可以是网络路径也可以是本地路径 本地路径mac下的格式是 video : audio 这里表示获取第一个音频设备int result = avformat_open_input(&av_context, name, format, &options);if (result != 0) {char errors[1024];// 根据返回值生成错误信息av_make_error_string(errors, 1024, result);printf("打开设备失败:%s\n", errors);return;}printf("打开设备成功!\n");get_audio_packet(av_context,&packet_callback);// 关闭输入 上下文avformat_close_input(&av_context);}
  • 读取数据和存储到文件
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {// w == 写  b == 二进制  + == 没有就创建文件FILE *f = fopen("/Users/cunw/Desktop/learning/音视频学习/音视频文件/code_recorder.pcm", "wb+");AVPacket *packet = av_packet_alloc();int result = -1;// 循环读取设备信息// result == -35 是Resource temporarily unavailable 因为获取太频繁 设备未准备好,还正在处理数据  // 因为输入设备准备好需要时间  睡一秒后再读取  sleep(1.0);while ((result = av_read_frame(·context, packet)) == 0  || result == -35) {if (packet->size > 0) {packet_callback(*packet);fwrite(packet->data, packet->size, 1, f);// 每读取一次 就清空数据包 不然数据包会一直增大av_packet_unref(packet);}}if (result != 0) {char errors[1024];av_make_error_string(errors, 1024, result);printf("get packet occured error is \"%s\" \n", errors);}// 将缓冲区剩余的数据 强制写入文件fflush(f);fclose(f);// 释放packet空间av_packet_free(&packet);}
// 回调函数
void packet_callback(AVPacket packet) {printf("packet size is %d\n",packet.size);}
  • 播放
  • ffplay 播放pcm数据: ffplay -ar(采样率) 44100 -ac(通道数) 2 -f(采样大小)f32le 文件名

音频编解码实战

音频重采样

就是将音频三元组(采样率 采样大小 通道数)的值转成另外一组值

1. 应用场景:

1、从设备采集的音频数据与编码器要求的不一致;
2、扬声器要求的音频数据与要播放的音频数据不一致;
3、方便运算:例如回音消除 将多声道变为单声道;

2. 如何判断是否需要重采样

  • 了解音频设备的参数
  • 查看ffmpeg源码

3. 重采样的步骤

api:需要使用libswresample库

1. 创建重采样上下文

 - swr_alloc_set_opts 通过设置采样参数获取上下文      

2. 设置参数

- 参数大体分为输出的采样率、采样大小、声道和输入的采样率、采样大小、声道;
- out_ch_layout:表示声道也可以是布局(扬声器的布局)AV_CH_LAYOUT_STEREO  立体声;
- out_sample_fmt:输出的采样格式 16 = AV_SAMPLE_FMT_S16 或者 32 =  AV_SAMPLE_FMT_FLT;
-  av_sample_fmt_s16 in_ch_layout:输入的声道布局  ;
-  in_sample_fmt 输入的采样格式 ;
-  in_sample_rate:  输入的采样率;
-  后两位是log相关 0,null

3. 初始化重采样

- swr_init 初始化上下文  

4. 进行重采样

- swr_convert 开始转换 ,目的就是将输入缓冲区的数据写入输出缓冲区out:输出结果缓冲区 out_count:每个通道的采样数 in:输入的缓冲区 in_count:输入的单个通道的采样数  
- 因为重采样的数据需要重新构造所以需要创建输入缓冲区和输出缓冲区  使用av_sample_array_and_samples audio_data创建其中的单通道采样数(单位是字节)`nb_samples = pkt.size / (32位 / 8) / 2(通道数)` linessize:缓冲区大小  align:对齐 0 
- 在转换前需要将pkt的data按字节拷贝到输入缓冲区,调用memcpy需要引用string.h- 将输出数据写入文件将输出缓冲区已经转换的数据写入文件

5. 释放资源

- 还有输入输出缓冲区的释放av_freep
- swr_free释放上下文

重采样上下文初始化代码

SwrContext * init_swr_context(void) {SwrContext *context = NULL;// 假设已经提前知道输入音频数据的三要素的值 AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLT, 44100context = swr_alloc_set_opts(NULL,AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_S16,44100,AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_FLT,44100,0, NULL);int result = swr_init(context);if (result != 0) {char error[1024];av_make_error_string(error, 1024, result);printf("初始化重采样上下文失败:%s", error);}return context;
}

将采集的数据重采样后 写入文件代码

void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {// w == 写  b == 二进制  + == 没有就创建文件FILE *f = fopen("/Users/cunw/Desktop/learning/音视频学习/音视频文件/resample.pcm", "wb+");// 初始化重采样上下文SwrContext *swr_context = init_swr_context();// 初始化转换的输入输出缓冲区uint8_t **out_buffer = NULL;int linesize_out = 0;av_samples_alloc_array_and_samples(&out_buffer, &linesize_out, 2, 512, AV_SAMPLE_FMT_S16, 0);uint8_t **in_buffer = NULL;int linesize_in = 0;// nb_samples 单通道采样数 4096 / (32 / 8) / 2 = 1024av_samples_alloc_array_and_samples(&in_buffer, &linesize_in, 2, 512, AV_SAMPLE_FMT_FLT, 0);AVPacket *packet = av_packet_alloc();int result = -1;// 循环读取设备信息// result == -35 是Resource temporarily unavailable 因为获取太频繁 设备未准备好,还正在处理数据sleep(1);while (((result = av_read_frame(context, packet)) == 0  || result == -35) && isRecording == 1) {if (packet->size > 0) {// 开始转换数据// 先将音频数据拷贝到输入缓冲区  只是重采样音频的话  只需要处理数组的第一个memcpy(in_buffer[0], packet->data, packet->size);// 再进行转换swr_convert(swr_context, out_buffer, 512, (const uint8_t **)in_buffer, 512);fwrite(out_buffer[0],linesize_out, 1, f);// 每读取一次 就清空数据包 不然数据包会一直增大av_packet_unref(packet);}}if (result != 0) {char errors[1024];av_make_error_string(errors, 1024, result);printf("get packet occured error is \"%s\" \n", errors);}// 释放重采样资源if (in_buffer) {av_freep(&in_buffer[0]);}if (out_buffer) {av_freep(&out_buffer[0]);}av_freep(&in_buffer);av_freep(&out_buffer);swr_free(&swr_context);// 将缓冲区剩余的数据 强制写入文件fflush(f);fclose(f);// 释放packet空间av_packet_free(&packet);}

ffmpeg 音频数据编码

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~

在使用fdk_aac编码器的时候,由于默认的ffmpeg有自带的aac,所以通过avcodec_find_encoder_by_name("libfdk_aac")就获取不到。在编译的时候加上--enable-libfdk-aac。注意:重新编译安装ffmpeg之前最好先删掉之前的ffmpeg,然后更新项目中的动态库;
如果还不行,试试单独下载安装[fdk_aac](https://www.linuxfromscratch.org/blfs/view/svn/multimedia/fdk-aac.html),再重新编译ffmpeg

  1. 创建编码器 avcodec
    1. avcodec_find_encoder 一种通过名字查找 一种是通过id查找,id的查找方式只会找默认的编码器,比如aac,如果是fdkaac就需要通过名字查找;
  2. AV_CODEC_ID_AAC | opus 其他编码器
  3. "libfdk_aac", aac默认的规格是AAC LC
  4. 创建上下文 avcodexcontext
    设置音频三要素
  5. avcodec_alloc_context3
    3表示第三个版本
  6. sample_fmt = av_sample_FMT_S16 aac编码器不支持flt 32位
  7. chnnel_layout = AV_CH_LAYOUT_STEREO( 或者chanels = 2)
  8. sample_rate = 44100
  9. bit_rate = 64000; (KB 码率)可选设置
  10. profile = FF_PROFILE_AAC_HE_V2; (只有bit_rate=0 才有用) 可选设置,设置编码器规格
  1. 打开编码器
  2. avcodex_opne2
    2表示第二个版本
    送数据给编码器时,编码器内部有一个缓冲区,缓冲一部分数据后才进行编码
  1. 编码
  2. 用AVFrame包装未编码的数据,相当于是个输入,用AVPacket包装已编码的数据,相当于是个输出;
  3. 调用avcodec_send_frame 将avframe缓冲区的数据发送给编码器,如果返回值大于0,就表示数据成功发送到了编码器,接着就可以通过循环使用 avcodec_receive_packet读取编码好的数据到AVPacket,并写入文件中,如果读取的结果是AVERROR(EAGIN)或者是AVERROR_EOF,就停止读取,如果是其他的负数,就停止编码;
  4. av_frame_alloc 堆区初始化frame
  5. 设置frame的nb_samples 单通道一个数据帧采样数 512
  1. format 每个采样的大小 av_sample_fmt_s16
  2. channel_layout 声道 av_ch_layout_stereo
  3. av_frame_get_buffer 分配frame里面buffer的大小
  4. 还要判断frame的buffer是否分配成功
  5. 将重采样后的数据memcpy到frame->data中
  6. 再将frame中的数据塞到编码器上下文中 avcodec_send_frame,该函数会返回一个int , 当结果>=0的时候表明有数据已经在编码缓冲区了;
  7. avcodec_receive_packet 读取编码好的数据 avpacket
  8. av_packet_alloc 分配编码后的数据空间
  9. 因为编码器上下文中有一个缓冲区,其中会缓存多个frame,因此并不是每塞一个frame就会有一个packet出来,所以需要通过一个while循环判断编码器的数据是否>=0,再通过avcodec_receive_packet获取packet,该函数也会返回一个int,如果返回值>=0表明获取成功,如果失败直接退出编码,这个值返回值还有其他含义,需要判断eagain 表明编码器没有数据了或者是有数据但是不够编码 这个eagain需要用AVERROR包装成一个负数,表明数据还没准备好 averror_eof 表明一点数据都没有了;
  10. 最后将数据编码后的数据写入到文件pkt->data,数据格式就是aac了;
  11. 在停止录制的时候,由于编码的缓存区可能还有数据,在最后关闭之前,再去取一遍编码数据放入文件;
  1. 释放资源

在结束的时候释放frame(av_frame_free) 和packet(av_packet_frame);

编码实战代码:

1. 创建fdk_aac编码器及上下文

AVCodecContext* init_codec_context(void) {// 创建aac编码器AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");// 初始化上下文AVCodecContext *context = NULL;context = avcodec_alloc_context3(codec);context->sample_fmt = AV_SAMPLE_FMT_S16;context->sample_rate = 44100;context->channel_layout = AV_CH_LAYOUT_STEREO;context->bit_rate = 0;// bitrate == 0 才会生效context->profile = FF_PROFILE_AAC_HE_V2;int result = avcodec_open2(context, codec, NULL);if (result < 0) {char error[1024];av_make_error_string(error, 1024, result);av_log(NULL, AV_LOG_DEBUG, "创建AAC编码器失败:%s",error);}return context;}

2. 创建输入缓冲区

AVFrame* create_audio_input_frame(void) {AVFrame *codec_frame = NULL;codec_frame = av_frame_alloc();codec_frame->nb_samples = 512;codec_frame->channel_layout = AV_CH_LAYOUT_STEREO;codec_frame->format = AV_SAMPLE_FMT_S16;int buffer_result = av_frame_get_buffer(codec_frame, 0);if (buffer_result < 0) {char error[1024];av_make_error_string(error, 1024, buffer_result);printf("frame 缓冲区分配失败:%s", error);}return codec_frame;
}

3. 开始编码并写入文件

void audio_encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *packet, FILE *fl) {// 将数据送入编码器int codec_result = avcodec_send_frame(ctx, frame);while (codec_result >= 0) {// 从packet中循环读取编码好的数据codec_result = avcodec_receive_packet(ctx, packet);if (codec_result == AVERROR(EAGAIN) || codec_result == AVERROR_EOF) {break;} else if (codec_result < 0) {char error[1024];av_make_error_string(error, 1024, codec_result);printf("编码器出错:%s     停止编码", error);} else {fwrite(packet->data, 1,packet->size, fl);}}if (codec_result < 0) {char error[1024];av_make_error_string(error, 1024, codec_result);printf("将数据送入编码器错误: %s\n",error);}
}

4. 调用

  • 先将重采样的数据放入avframe的缓冲区中
memcpy(codec_frame->data[0], out_buffer[0], linesize_out);
  • 再开始编码
audio_encode(codec_context, codec_frame, codec_packet, f);
  • 总览
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {// w == 写  b == 二进制  + == 没有就创建文件FILE *f = fopen("/Users/cunw/Desktop/learning/音视频学习/音视频文件/encoder.aac", "wb+");// 创建编码器上下文AVCodecContext *codec_context = init_codec_context();// 初始化输入缓冲区  AVframeAVFrame *codec_frame = create_audio_input_frame();// 初始化编码输出缓冲区AVPacket *codec_packet = av_packet_alloc();// 初始化重采样上下文SwrContext *swr_context = init_swr_context();// 初始化重采样的缓冲区uint8_t **out_buffer = NULL;int linesize_out = 0;uint8_t **in_buffer = NULL;int linesize_in = 0;init_resammple_buffer(&in_buffer, &linesize_in, &out_buffer, &linesize_out);AVPacket *packet = av_packet_alloc();int result = -1;// 循环读取设备信息while (isRecording == 1) {result = av_read_frame(context, packet);if (packet->size > 0 && result == 0) {packet_callback(*packet);// 开始转换数据// 先将音频数据拷贝到输入缓冲区  只是重采样音频的话  只需要处理数组的第一个memcpy(in_buffer[0], packet->data, packet->size);// 再进行转换swr_convert(swr_context, out_buffer, 512, (const uint8_t **)in_buffer, 512);// 将重采样好的数据按字节拷贝到frame缓冲区memcpy(codec_frame->data[0], out_buffer[0], linesize_out);audio_encode(codec_context, codec_frame, codec_packet, f);// 每读取一次 就清空数据包 不然数据包会一直增大av_packet_unref(packet);} else if (result == -EAGAIN) {// result == -35 是Resource temporarily unavailable 因为设备未准备好,还正在处理数据av_usleep(1);}}// 把缓冲区剩余的数据拿出来编码audio_encode(codec_context, NULL, codec_packet, f);if (result != 0) {char errors[1024];av_make_error_string(errors, 1024, result);printf("get packet occured error is \"%s\" \n", errors);}// 释放重采样资源if (in_buffer) {av_freep(&in_buffer[0]);}if (out_buffer) {av_freep(&out_buffer[0]);}av_freep(&in_buffer);av_freep(&out_buffer);swr_free(&swr_context);av_frame_free(&codec_frame);av_packet_free(&codec_packet);// 将缓冲区剩余的数据 强制写入文件fflush(f);fclose(f);// 释放packet空间av_packet_free(&packet);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/130111.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

汽车标定技术(一):XCP概述

目录 1.汽车标定概述 2.XCP协议由来及版本介绍 3.XCP技术通览 3.1 XCP上下机通信模型 3.2 XCP指令集 3.2.1 XCP帧结构定义 3.2.2 标准指令集 3.2.3 标定指令集 3.2.4 页切换指令集 3.2.5 数据采集指令集 3.2.6 刷写指令集 3.3 ECU描述文件(A2L)概述 3.3.1 标定上位…

C++中何时及如何使用析构函数

C中何时及如何使用析构函数 析构函数不返回任何值&#xff0c;没有返回类型&#xff0c;也没有函数参数。由于没 有函数参数&#xff0c;因此它不能被重载。换言之&#xff0c;一个类可以有多个构造函数&#xff0c;但是只能有一个析构函数。 何时调用析构函数&#xff1a; &…

有方N58 HTTP POST 请求连接 TDengine

串口调试软件&#xff1a;格西调试精灵 第一步先注册网络获取IP地址 建立PPP连接 ATXIIC1\r PPP链路建立成功&#xff0c;查询IP地址 ATXIIC?\r 设置网络APN ATCREG?\r 运行结果&#xff0c;红线处是获…

js:可选链运算符(?.)和空值合并运算符(??)

文档&#xff1a; 可选链运算符&#xff08;?.&#xff09;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining空值合并运算符&#xff08;??&#xff09;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Referenc…

【算法与数据结构】--算法和数据结构的进阶主题--并行算法和分布式数据结构

一、并行算法 1.1 并行计算概述 并行计算是一种计算方法&#xff0c;旨在通过同时执行多个计算任务来提高计算性能和效率。与传统的串行计算不同&#xff0c;其中每个任务按顺序执行&#xff0c;并行计算允许多个任务同时执行。这种并行性通常通过将计算任务分解为较小的子任…

算法:Java构建二叉树并迭代实现二叉树的前序、中序、后序遍历

先自定义一下二叉树的类&#xff1a; // Definition for a binary tree node. public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) { this.val val; }TreeNode(int val, TreeNode left, TreeNode right) {this.val val;this.left…

MongoDB安装及开发系例全教程

一、系列文章目录 一、MongoDB安装教程—官方原版 二、MongoDB 使用教程(配置、管理、监控)_linux mongodb 监控 三、MongoDB 基于角色的访问控制 四、MongoDB用户管理 五、MongoDB基础知识详解 六、MongoDB—Indexs 七、MongoDB事务详解 八、MongoDB分片教程 九、Mo…

ATE新能源汽车充电桩自动负载测试系统

随着新能源汽车的普及&#xff0c;充电桩的需求也在不断增加&#xff0c;为了确保充电桩的性能和安全性&#xff0c;对其进行负载测试是非常重要的。ATE新能源汽车充电桩自动负载测试系统是一种专门用于检测充电桩性能的设备&#xff0c;它可以模拟各种实际使用场景&#xff0c…

6、QtCharts 悬浮曲线效果

文章目录 效果dialog.hdialog.cpp悬浮槽函数 效果 dialog.h #ifndef DIALOG_H #define DIALOG_H#include <QDialog> #include <QtCharts> #include <QLineSeries> #include <QGraphicsScene> #include <QTimer> #include <QSplineSeries>…

《实战:如何搭建一个完整的 Vue2.0 项目》- 7、Vue2.x 项目 webpack 4 升级 5(半自动升级)

1.自动升级 先全局安装升级插件 npm i npm-check npm-check-updates -g检查依赖 npm-check更新检查后的依赖并展示版本号&#xff0c;此时 package.json还没有更新 npm-check-updates升级 package.json&#xff0c;下图显示更新版本&#xff0c;此时 package.json文件已变更…

【JavaScript】变量提升

变量提升&#xff08;Hoisting&#xff09;被认为是&#xff0c;Javascript 中执行上下文&#xff08;特别是创建和执行阶段&#xff09;工作方式的一种认识。 提升&#xff08;Hoisting&#xff09;这个词。不过&#xff0c;需要注意的是&#xff0c;开始时&#xff0c;这个概…

FFmpeg——使用Canvas录制视频尚存问题的解决方案

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

编译正点原子LINUXB报错make: arm-linux-gnueabihf-gcc:命令未找到

编译正点原子LINUX报错make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到 1.报错内容2.解决办法3./bin/sh: 1: lzop: not found4.编译成功 1.报错内容 make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到CHK include/config/kernel.releaseCHK include/generat…

lamba stream处理集合

lamba stream处理集合 带拼接多字段分组List< Object> 转 Map<String,List< Object>> Map<String, List<ProfitAndLossMapping>> collect plMappingList.stream() .collect(Collectors.groupingBy(m -> m.getLosType() ":" m.…

Linux 上的轻量级浏览器

导读大多数 Linux 桌面环境中包含的基本图像查看器可能不足以满足你的需要。如果你想要一些更多的功能&#xff0c;但仍然希望它是轻量级的&#xff0c;那么看看这四个 Linux 桌面中的图像查看器&#xff0c;如果还不能满足你的需要&#xff0c;还有额外的选择。 当你需要的不…

本地部署Jellyfin影音服务器并实现远程访问影音库

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

表格冻结第二行

在网上找了一圈也没找到说明白的。 像这样的表格下面会有很多行&#xff0c;往下翻的时候会忘记第二行显示的什么内容。 需求&#xff1a;将第二行进行冻结 实现&#xff1a; 1&#xff0c;选中第一和第二行&#xff0c;下拉&#xff0c;点击冻结 2&#xff0c;会显示冻结至…

会声会影2024出来了吗?会声会影2024这款视频剪辑软件怎么样?

众所周知&#xff0c;每每有新兴行业逐渐崛起壮大的时候&#xff0c;随机而来的就是这个行业创造出的衍生行业&#xff0c;比如说现在的短视频平台或者是视频剪辑行业&#xff0c;都是很明显例子&#xff0c;今天我们就针对剪辑软件来和大家聊一聊&#xff0c;会声会影2024这款…

自动化测试篇:Java+selenium+appium自动化测试详解

一、启动测试机或者Android模拟器&#xff08;Genymotion俗称世界上最快的模拟器&#xff0c;可自行百度安装&#xff09; 同时&#xff0c;我也准备了一份软件测试视频教程&#xff08;含接口、自动化、性能等&#xff09;&#xff0c;需要的可以直接在下方观看&#xff0c;或…