【FFmpeg实战】音视频解封装格式

news2024/11/19 8:37:07

转载自原文地址:https://www.cnblogs.com/zuojie/p/16798273.html

一、什么是封装格式

封装格式也称为容器,用于打包音频、视频以及字幕等等,比如常见的容器有MP4、MOV、WMV、FLV、AVI、MKV等等。容器里面装的是音视频的压缩帧,但是不是所有类型的压缩帧都可以装入容器中,不同的容器对于压缩帧的格式是有要求的,有一些容器的兼容性要好一些,有一些容器的兼容性就会差一些。

我们平时看到的文件后缀名mp4或者mov是指文件格式,它的作用是让我们知道它是何种类型的文件,让操作系统知道打开文件时应该用哪个应用打开。正常来讲文件后缀名是和封装格式是有对应关系的,每个容器都有一个或多个文件后缀名。虽然说我们可以随意修改文件后缀名,但是封装格式属于文件的内部结构,而文件格式是文件外在表现,所以修改文件扩展名是无法修改容器原封装格式的,修改后播放器一般情况下也是可以播放的,因为播放器在播放时会打开文件判断是哪种容器。

二、使用 FFmpeg 实现解封装

现在对封装格式有了一个简单了解,接下来了解一下封装格式数据是如何被播放出来的,首先要对封装格式数据解封装,可以得到音频压缩数据和视频压缩数据,然后再对音频压缩数据和视频压缩数据分别进行解码,就得到了音频原始数据和视频原始数据,最后对音频原始数据进行处理送到扬声器,对视频数据进行处理送到屏幕,并且还要进行音视频同步处理。本文主要分享的是如何从封装格式数据中拿到音频原始数据和视频原始数据各自生成相对于的文件,音视频同步处理先不讨论。封装格式数据播放大致实现流程图如下:

img

下面开始使用FFmpeg的libavformat库(它是一个包含用于多媒体容器格式的解复用器和复用器的库)从MP4封装格式中解码出YUV数据(原始视频数据)和PCM数据(原始音频数据)。

1、创建解封装上下文打开流媒体文件

int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

参数说明:

  • ps:指向解封装上下文的指针,由avformat_alloc_context创建。如果传nullptr,函数avformat_open_input内部会帮我们创建解封装上下文(注意:函数调用失败时,会释放开发者手动创建的解封装上下文);
  • url:要打开的流的url,也就是要打开的流媒体文件(此处也可以传入设备名称和设备序号,我们在音视频录制时传入的就是设备序号);
  • fmt:如果非nulllptr将使用特定的输入格式,传nulllptr将自动检测输入格式;
  • options:包含解封装上下文和解封装器特有的参数的字典。 最后不要忘记使用函数avformat_close_input关闭解封装上下文,使用函数avformat_close_input就不需要再调用函数avformat_free_context了,其内部帮我们调用了函数avformat_free_context。

2、检索流信息

2.1、检索流信息

该函数可以读取一部分音视频数据并且获得一些相关的信息:

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

参数说明:

  • ic:需要读取信息的解封装上下文;
  • options:额外一些参数。

2.2、导出流信息到控制台

我们可以使用下面函数打印检索到的详细信息到控制台,包括音频流的采样率、通道数等,视频流包括视频的width、height、pixel format、码率、帧率等信息:

void av_dump_format(AVFormatContext *ic,
                    int index,
                    const char *url,
                    int is_output);

参数说明:

  • ic:需要打印分析的解封装上下文;
  • index:需要导出信息的流索引;
  • url:需要打印的输入或者输出流媒体文件 url。
  • is_output:是否输出,0 = 输入 / 1 = 输出;

在QT中直接调用这个方法,还不能打印出信息,通过看ffmpeg的官方例子里的信息可以了解到是要dump到stderr的控制台:

img

因此在Qt中还需要调用fflush(stderr)才能够将信息输出到控制台。fflush会强迫将缓冲区内容清空,就会立即输出所有在缓冲区中的内容。stderr是指标准错误输出设备,输出的文本内容一般是红色的,默认向屏幕输出内容。

打印的信息如下,这和我们在终端看到的信息是一样的:

img

看这个打印是不是很眼熟,跟我们在命令行中敲ffmpeg相关命令时打印的一样的

img

3、初始化音频解码器查找合适的音视流和视频流信息

读取多媒体文件音频流和视频流信息,函数av_find_best_stream是在FFmpeg新版本中添加的,老版本只可通过遍历的方式读取,我们可以通过stream->codecpar->codec_type判断流类型,可以取得同样的效果:

int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);

参数说明:

  • ic:需要处理的流媒体文件,解封装上下文中包含流媒体文件信息;
  • type:要检索的流类型,比如音频流、视频流和字幕流等等;
  • wanted_stream_nb:请求的流序号,传 -1 自动选择;
  • related_stream:查找相关流,不查找传 -1;
  • decoder_ret:返回当前流对应的解码器。函数调用成功,并且参数 decoder_ret 不为 nullptr,将通过参数 decoder_ret 返回一个对应的解码器;
  • flags:目前没有定义;

AVMediaType流类型枚举:

enum AVMediaType {
    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
    AVMEDIA_TYPE_VIDEO,
    AVMEDIA_TYPE_AUDIO,
    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
    AVMEDIA_TYPE_SUBTITLE,
    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
    AVMEDIA_TYPE_NB
};

函数调用成功返回流序号,如果未找到请求类型的流返回AVERROR_STREAM_NOT_FOUND,如果找到了请求的流但是没有对应的解码器将返回AVERROR_DECODER_NOT_FOUND。

4、检验流

我们成功的查找到流后最好要检验一下流是否真的存在;

AVStream *stream = _fmtCtx->streams[*streamIdx];
if (!stream) {
    qDebug() << "stream is empty";
    return -1;
}

5、查找解码器

我们通过stream->codecpar->codec_id可以查找到对应的解码器:

AVCodec *avcodec_find_decoder(enum AVCodecID id);

参数说明:

  • codec:解码器;

最后需要使用函数avcodec_free_context释放解码上下文。

6、拷贝流参数到解码器

在FFmpeg旧版本中保存流信息参数是AVStream结构体中的codec字段。新版本中已经将AVStream结构体中的codec字段定义为废弃属性。因此无法像以前旧版本中直接通过参数codec获取流信息。当前版本保存流信息的参数是AVStream结构体中的codecpar字段,FFmpeg提供了函数avcodec_parameters_to_context将流信息拷贝到新的解码器中:

int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par);

参数说明:

  • codec:解码器;
  • par:流中的参数,通过stream->codecpar获取;

7、打开解码器

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

参数说明:

  • avctx:需要初始化的解码上下文;
  • codec:解码器;
  • options:包含解封装上下文和解封装器特有的参数的字典。

8、从音视频流中读取压缩帧

我们可以通过pkt->stream_index判断读取到的压缩帧是音频压缩帧还是视频压缩帧等等,然后分别对音视频压缩数据进行解码:

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

参数说明:

  • s:解封装上下文;
  • pkt:读取到的压缩帧数据;

在调用函数avcodec_send_packet之前我们需要创建一个AVPacket。在FFmpeg版本4.4中av_init_packet函数已经过期,实际上FFmpeg不建议我们把AVPacket放到栈空间了。建议使用函数av_packet_alloc来创建,av_packet_alloc创建的AVPacket是在堆空间的。下面写法不提倡:

// pkt 是在函数中定义,pkt 内存在栈空间,所以 pkt 内存不需要我们去申请和释放
AVPacket pkt;
// init 仅仅是初始化,并不会分配内存
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;

最后需要使用函数av_packet_free释放AVPacket,注意函数av_packet_unref仅仅是把AVPacket指向的一些额外内存释放掉,并不会释放AVPacket内存空间。

9、音视频解码

9.1发送压缩数据到解码器

首先使用函数avcodec_send_packet发送压缩数据到解码器:

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

9.2获取解码后的数据

然后使用函数avcodec_receive_frame从解码器中读取解码后的数据:

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

10、保存音视频输出参数

我们定义了两个结构体分别保存音频输出参数和视频输出参数:

// 音频输出参数
typedef struct {
    const char *filename; // 文件名
    int sampleRate; // 采样率
    AVSampleFormat sampleFmt; // 采样格式
    int chLayout; // 声道布局
} AudioDecodeSpec;
 
// 视频输出参数
typedef struct {
    const char *filename; // 文件名
    int width; // 宽
    int height; // 高
    AVPixelFormat pixFmt; // 像素格式
    int fps; // 帧率
} VideoDecodeSpec;

保存音频参数:

_aOut->sampleRate = _aDecodeCtx->sample_rate;
_aOut->sampleFmt = _aDecodeCtx->sample_fmt;
_aOut->chLayout = _aDecodeCtx->channel_layout;

保存视频参数:

_vOut->width = _vDecodeCtx->width;
_vOut->height = _vDecodeCtx->height;
_vOut->pixFmt = _vDecodeCtx->pix_fmt;
_vOut->fps = _vDecodeCtx->framerate.num / _vDecodeCtx->framerate.den;

通过上面方法获取到的帧率有可能是0,我们需要使用函数av_guess_frame_rate获取帧率:

AVRational framerate = av_guess_frame_rate(_fmtCtx, _fmtCtx->streams[_vStreamIdx], nullptr);
_vOut->fps = framerate.num / framerate.den;

10、音视频原始数据写入文件

10.1、音频原始数据写入文件

我们的最终目的是将音频原始数据写入到PCM文件并使用ffplay命令进行播放。因为播放器是不支持播放planar格式数据的,所以要求写入文件的数据为非planar格式。我们可以通过函数av_sample_fmt_is_planar来判断当前音频原始数据是否为planar格式,对于planar格式数据,我们要把每个声道中的音频样本交错写入文件。非planar格式直接写入文件即可:

img

// _sampleSize 每个音频样本的大小
_sampleSize = av_get_bytes_per_sample(_aOut->sampleFmt);
// _sampleFrameSize 每个音频样本帧的大小
_sampleFrameSize = _sampleSize * _aDecodeCtx->channels;
 
 
void Demuxer::writeAudioFrame(){
    // libfdk_aac解码器,解码出来的PCM格式:s16
    // aac解码器,解码出来的PCM格式:ftlp
    if(av_sample_fmt_is_planar(_aOut->sampleFmt)){// 是planar格式
        // 外层循环:每一个声道的样本数
        // si = sample index
        for (int si = 0; si < _frame->nb_samples; si++) {
           for (int ci = 0; ci < _aDecodeCtx->channels; ci++) {
               uint8_t *begin = (uint8_t *)(_frame->data[ci] + _sampleSize * si);
               _aOutFile.write((char *)begin, _sampleSize);
           }
       }
    }else{// 非planar格式
//        _aOutFile.write((char *)_frame->data[0],_frame->linesize[0]);
//        int v1 = _frame->linesize[0];
//        int v2 = _frame->nb_samples // 多少个样本
//                * av_get_bytes_per_sample(_aOut->sampleFmt) // 每个样本多大
//                * _aDecodeCtx->channels;// 声道
//        if(v1 != v2){
//            qDebug() <<"_frame->linesize[0]"<<v1 <<"_frame->nb_samples "<< v2;
//        }
        _aOutFile.write((char *)_frame->data[0],_frame->nb_samples * _sampleFrameSize);
    }
}

函数av_sample_fmt_is_planar内部会去sample_fmt_info表中查询当前采样格式是否为planar格式:

// 源码片段 ffmpeg-4.3.2/libavutil/samplefmt.c
/** this table gives more information about formats */
static const SampleFmtInfo sample_fmt_info[AV_SAMPLE_FMT_NB] = {
    [AV_SAMPLE_FMT_U8]   = { .name =   "u8", .bits =  8, .planar = 0, .altform = AV_SAMPLE_FMT_U8P  },
    [AV_SAMPLE_FMT_S16]  = { .name =  "s16", .bits = 16, .planar = 0, .altform = AV_SAMPLE_FMT_S16P },
    [AV_SAMPLE_FMT_S32]  = { .name =  "s32", .bits = 32, .planar = 0, .altform = AV_SAMPLE_FMT_S32P },
    [AV_SAMPLE_FMT_S64]  = { .name =  "s64", .bits = 64, .planar = 0, .altform = AV_SAMPLE_FMT_S64P },
    [AV_SAMPLE_FMT_FLT]  = { .name =  "flt", .bits = 32, .planar = 0, .altform = AV_SAMPLE_FMT_FLTP },
    [AV_SAMPLE_FMT_DBL]  = { .name =  "dbl", .bits = 64, .planar = 0, .altform = AV_SAMPLE_FMT_DBLP },
    [AV_SAMPLE_FMT_U8P]  = { .name =  "u8p", .bits =  8, .planar = 1, .altform = AV_SAMPLE_FMT_U8   },
    [AV_SAMPLE_FMT_S16P] = { .name = "s16p", .bits = 16, .planar = 1, .altform = AV_SAMPLE_FMT_S16  },
    [AV_SAMPLE_FMT_S32P] = { .name = "s32p", .bits = 32, .planar = 1, .altform = AV_SAMPLE_FMT_S32  },
    [AV_SAMPLE_FMT_S64P] = { .name = "s64p", .bits = 64, .planar = 1, .altform = AV_SAMPLE_FMT_S64  },
    [AV_SAMPLE_FMT_FLTP] = { .name = "fltp", .bits = 32, .planar = 1, .altform = AV_SAMPLE_FMT_FLT  },
    [AV_SAMPLE_FMT_DBLP] = { .name = "dblp", .bits = 64, .planar = 1, .altform = AV_SAMPLE_FMT_DBL  },
};

Planar格式

img

_frame->linesize[0]如果是视频表示的是0号平面它每一行的大小,而音频没有行的概念,是表示一个平面有多大。在音频中,planar格式每个声道的大小都是一样的,所有只有linesize[0]有值,linesize[1]是没有值的。

img

注意:

libfdk_aac解码器,解码出来的PCM格式:s16(非Planar格)

aac解码器,解码出来的PCM格式:ftlp(Planar格)

img

非Planar格式

img

在之前的AAC解码实战那里是按照的方式进行文件的写入:

_aOutFile.write((char *)_frame->data[0],_frame->linesize[0]);

但是这里如果使用linesize[0]来写入数据,会出现写入的文件大小比ffmpeg生成的文件大一些,相差了3072,由于是双声道的s16,所以一个样本帧是4个字节,有3072/4 = 768个样本帧。

img

可以通过下面的打印知道4096 -1024正好是3072

int v1 = _frame->linesize[0];
int v2 = _frame->nb_samples // 多少个样本
        * av_get_bytes_per_sample(_aOut->sampleFmt) // 每个样本多大
        * _aDecodeCtx->channels;// 声道
if(v1 != v2){
    qDebug() <<"_frame->linesize[0]"<<v1 <<"_frame->nb_samples "<< v2;
}

打印结果:

img

linesize是指缓冲区大小,而_frame->nb_samples是指frame里面解码出来有多少个音频样本,有可能frame中的样本数量并不足以填满缓冲区,所以它的大小不一定等于linesize[0],而是小于等于linesize[0]。所以inesize[0]还是有些不靠谱的.

img

10.2、视频原始数据写入文件

// 创建用于存放一帧解码图片的缓冲区
_imgSize = av_image_alloc(_imgBuf,_imgLinesizes,_vOut->width,_vOut->height,_vOut->pixFmt,1);
// _imgSize = av_image_get_buffer_size(_vOut->pixFmt,_vOut->width,_vOut->height,1);
 
 
void Demuxer::writeVideoFrame()
{
    // 拷贝frame的数据到_imgBuf缓冲区
    av_image_copy(_imgBuf,_imgLinesizes,(const uint8_t **)(_frame->data),_frame->linesize,
                  _vOut->pixFmt,_vOut->width,_vOut->height);
    // 将缓冲区的数据写入文件
    _vOutFile.write((char *)_imgBuf[0],_imgSize);
}

我们可以使用av_image_get_buffer_size方法来获取一张图片大小,但是这里的av_image_alloc方法的返回值就是一张图片大小,所以可以直接使用av_image_alloc方法的返回值。

11、关闭文件 & 释放资源

_aOutFile->close();
_vOutFile->close();
avcodec_free_context(&_aDecodeCtx);
avcodec_free_context(&_vDecodeCtx);
avformat_close_input(&_fmtCtx);
av_packet_free(&_pkt);
av_frame_free(&_frame);
av_freep(&_imgBuf[0]);

三、使用 FFmpeg 命令行解封装

$ ffmpeg -c:v h264 -c:a aac -i in.mp4 cmd_out.yuv -f f32le cmd_out.pcm

和使用FFmpeg命令行生成的PCM和YUV文件大小对比:

$ ls -al
-rw-r--r--   1 mac  staff          860790 Apr 20 12:38 in.mp4
-rw-r--r--   1 mac  staff         3459072 Apr 20 16:25 cmd_out.pcm
-rw-r--r--   1 mac  staff       125798400 Apr 20 16:25 cmd_out.yuv
-rw-r--r--   1 mac  staff         3459072 Apr 20 16:18 out.pcm
-rw-r--r--   1 mac  staff       125798400 Apr 20 16:18 out.yuv

四、总结

初始化解码器的流程(红框中)音频和视频都是一样的,仅仅AVMediaType不同,解码流程(绿框中)也是一样的。这部分代码音视频是可以共用的。整体流程参考下图:

img

   >>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864 
   >>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群: 739729163  领取

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

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

相关文章

5、基于Python所写的飞机大战设计

点击以下链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87953348?spm1001.2014.3001.5503 《彩图版飞机大战》程序使用说明 在PyCharm中运行《彩图版飞机大战》即可进入如图1所示的游戏界面。 图1 游戏主界面 具体的操作步骤如下&#xff…

ASEMI代理NXP可控硅BT139-600E参数,BT139-600E规格

编辑-Z BT139-600E参数描述&#xff1a; 型号&#xff1a;BT139-600E 断态重复峰值电压VDRM&#xff1a;600V RMS导通电流IT(RMS)&#xff1a;16A 非重复峰值导通电流ITSM&#xff1a;155A 峰值栅极电流IGM&#xff1a;2A 峰值栅极功率PGM&#xff1a;5W 储存温度Tstg&…

C++进阶—二叉搜索树Key、Key-Value模型应用及二叉树OJ(非递归实现遍历)

目录 1、二叉树搜索模型实现及应用场景 1. 二叉搜索树Key模型及其应用 2. 二叉搜索树Key-Value模型及其应用 2、二叉树OJ 2.1 根据二叉树创建字符串 2.2 二叉树的层序遍历I 2.3 二叉树的层序遍历II 2.4 二叉树的最近公共祖先 2.5 二叉搜索树及双向链表 2.6 从前序与中…

想学芯片专业?一文讲清楚芯片报考指南

2021年清北相继成立集成电路学院之后&#xff0c;国内在电子方面有实力积累的高校纷纷跟进&#xff0c;设立集成电路学院、开设集成电路相关专业。 直到今年&#xff0c;高校在集成电路方面的投入和培养力度&#xff0c;仍在持续并越来越大。 就在今年&#xff0c;示范性28所…

CDN是什么?

81. CDN是什么&#xff1f; CDN&#xff08;Content Delivery Network&#xff09; 即内容分发网络&#xff0c;是一种分布式的网络架构&#xff0c;旨在提高用户获取内容的速度、可用性和安全性。CDN通过将内容部署到全球各个节点服务器&#xff0c;并根据用户的地理位置将内…

有关软件检测实验室检测报告的全部知识点盘点

检测报告是实验室检测成果的重要载体&#xff0c;也是软件检测实验室质量管理水平的一个重要体现&#xff0c;本文我们就一起来看一下&#xff0c;软件检测实验室在申请cnas测试认证时&#xff0c;应该如何规范实验室的检测报告。 首先我们一起来看一下&#xff0c;cnas官方对…

记编译 Doris 的过程

目录 概述 1、关于clang 2、链接静态库的问题 3、 cmake的知识 4、关于nm 5、关于 ABI 6、关于debug info 概述 尝试编译 doris&#xff0c;其过程异常坎坷&#xff0c;不过也学到了很多东西。 首先&#xff0c;按照doris官网上的编译指导&#xff0c;下载ldb_toolchai…

python图像处理实战(三)—图像几何变换

&#x1f680;写在前面&#x1f680; &#x1f58a;个人主页&#xff1a;https://blog.csdn.net/m0_52051577?typeblog &#x1f381;欢迎各位大佬支持点赞收藏&#xff0c;三连必回&#xff01;&#xff01; &#x1f508;本人新开系列专栏—python图像处理 ❀愿每一个骤雨初…

Vue实例知识点分享

文章目录 导文下面是创建 Vue 实例的基本步骤 常用的 Vue 实例方法和属性总结 导文 Vue的实例是用来创建 Vue 应用程序的对象。通过实例化 Vue 构造函数&#xff0c;我们可以创建一个具有响应式数据、计算属性、方法和生命周期钩子等特性的 Vue 实例。 下面是创建 Vue 实例的基…

springDatajpa动态sql根据时间范围将数据导出为excel并使用vue的按钮去触发

用到的技术点&#xff1a; 1.springDatajpa 2.EasyExcel 3.数据库 4.vue 前端实现&#xff1a; 1.创建按钮&#xff08;点击此按钮弹出填写导出条件的弹出框&#xff09; <el-button type"primary" round click"dialogVisible true"><svg-icon …

Java如何实现分库分表

一、为啥要分库分表 在大型互联网系统中&#xff0c;大部分都会选择mysql作为业务数据存储。一般来说&#xff0c;mysql单表行数超过500万行或者单表容量超过2GB&#xff0c;查询效率就会随着数据量的增长而下降。这个时候&#xff0c;就需要对表进行拆分。 那么应该怎么拆分…

vite项目中处理各种静态资源的引入方式介绍

一、引用图片资源 在vite创建的vue3项目中&#xff0c;引用图片资源有以下两种方式&#xff1a; 直接在模板中使用路径引用&#xff1a;在模板中使用标签&#xff0c;通过src属性引用图片。例如&#xff1a; <template><div><img src"./assets/logo.png…

NetApp FAS 存储管理软件,海量非结构化数据存储

NetApp FAS 存储管理软件&#xff0c;海量非结构化数据存储 在 NetApp ONTAP 数据管理软件的支持下&#xff0c;帮助您构建简单、安全且值得信赖的存储基础架构。NetApp FAS 存储阵列可让客户同时兼顾性能和容量。 NetApp FAS 系统经过优化&#xff0c;易于部署和操作&#x…

记录ip段解析成ip

无脑记录者记录使用方法 1.源代码链接 https://github.com/codeexpress/cidr2ip 2.提前准备的内容 go开发语言&#xff0c;链接里面的main.go 3.使用方法 直接新增文件cidrs.txt cidrs.txt文件里面加入需要解析的ip段即可

【实战】 JWT、用户认证与异步请求(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(四)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求1.login2.middleware of json-server3.jira-dev-tool&#xff08;imooc-jira-tool&#xff09;安装问…

机器学习——自然语言处理(一)

1 分词 1.1 设计原则 切分粒度大&#xff1b;非字典词少、单字字典词少&#xff1b;总体次数少。 1.2 基于词典匹配的分词 1.3 基于语法和规则的分词 目前处在试验阶段 1.4 基于统计的分词 1.5 技术难点 1.5.1 歧义识别 交集型歧义&#xff1a;AB | C or A | BC 组合型…

Jmeter操作数据库运行提示“Cannot load JDBC driver class ‘com.mysql.jdbc.Driver‘”的有效解决

如图所示&#xff0c;在jmeter中运行sql时报错提示“Cannot load JDBC driver class com.mysql.jdbc.Driver” 原因分析&#xff1a;这是因为没有mysql驱动&#xff0c;需要下载对应的jar包 一、下载地址&#xff1a;MySQL :: Download Connector/J 根据需求选择下载&#xf…

数字化转型:智慧物业行业落地与应用的突围之路!

导语 | 红杉中国在《2021 年企业数字化年度指南》中指出&#xff0c;96% 的受访企业已经开展了数字化实践&#xff0c;而其中超过 6 成的受访者都表示期望在未来进一步增加数字化的投入。技术因素或将成为未来两到三年影响企业发展最为重要的外部力量。当前地产与物业行业进入不…

当前最强的免费AI画图、AI绘图工具-2

Midjourney比较贵&#xff0c;而且无法访问&#xff0c;Stable Diffusion部署起来很麻烦。网上有哪些可以直接在网页端或者下载的app可以实现AI画图的工具。我们整理了45个相关工具&#xff0c;这是系列2&#xff0c;收录到 当前最强的免费AI画图、AI绘图工具-2https://www.web…

【C++】-- 高并发内存池

高并发内存池 项目介绍池化技术内存池 定长内存池的实现整体框架threadcachethreadcache整体设计threadcache哈希桶映射对齐规则TLS无锁访问 centralcachecentralcache整体设计centralcache结构设计centralcache的实现 pagecachepagecache整体设计pagecache中获取Span 回收内存…