二、视频中提取音频
1.FFmpeg
bool AVInterface::extractAudio(const char* src, const char* dstDir) { if (NULL == src || NULL == dstDir) { printf("Ffmpeg::extractAudio[ERROR]::无效参数,请检查文件路径是否正确\n"); return false; } int ret = 0; // 预存原文件路径 const char* src_fileName = src; // 1.获取媒体文件的全局上下文信息 // 1.1 定义 AVFormatContext 容器 AVFormatContext* pFormatCtx = NULL; // AVFormatContext描述了一个媒体文件或者媒体流构成的基本信息 pFormatCtx = avformat_alloc_context(); // 为 pFormatCtx 申请内存 // 1.2 打开媒体文件,并且读取媒体文件的头信息放入pFormatCtx中 ret = avformat_open_input(&pFormatCtx, src_fileName, NULL, NULL); if (ret < 0) { printf("Ffmpeg::extractAudio[ERROR]::打开媒体流文件失败\n"); return false; } // 2.探测流出信息 // 2.1 探寻文件中是否存在信息流,如果存在则将多媒体文件信息流放到pFormatCtx ret = avformat_find_stream_info(pFormatCtx, NULL); if (ret < 0) { printf("Ffmpeg::extractAudio[ERROR]::文件中不存在信息流\n"); return false; } av_dump_format(pFormatCtx, 0, src_fileName, 0); // 打印封装格式和流信息 // 2.2 查找文件信息流中是否存在音频流(我们只需要提取音频),并获取到音频流在信息流中的索引 int audio_stream_index = -1; audio_stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); if (-1 == audio_stream_index) { printf("Ffmpeg::extractAudio[ERROR]::文件中不存在音频流\n"); return false; } // 3.输出容器的定义 AVFormatContext* pFormatCtx_out = NULL; // 输出格式的上下文信息 const AVOutputFormat* pFormatOut = NULL; // 输出的封装格式 AVPacket packet; // 输出文件路径 char szFilename[256] = { 0 }; snprintf(szFilename, sizeof(szFilename), "%s/ffmpeg-music.aac", dstDir); // 3.1 初始化容器 // 初始化一些基础的信息 av_init_packet(&packet); // 给 pFormatCtx_out 动态分配内存,并且会根据文件名初始化一些基础信息 avformat_alloc_output_context2(&pFormatCtx_out, NULL, NULL, szFilename); // 得到封装格式 AAC pFormatOut = pFormatCtx_out->oformat; // 4.读取音频流,并且将输入流的格式拷贝到输出流的格式中 for (int i = 0; i < pFormatCtx->nb_streams; ++i) // nb_streams 流的个数 { // 流的结构体,封存了一些流相关的信息 AVStream* out_stream = NULL; // 输出流 AVStream* in_stream = pFormatCtx->streams[i]; // 输入流 AVCodecParameters* in_codeper = in_stream->codecpar; // 编解码器 // 只取音频流 if (in_codeper->codec_type == AVMEDIA_TYPE_AUDIO) { // 建立输出流 out_stream = avformat_new_stream(pFormatCtx_out, NULL); if (NULL == out_stream) { printf("Ffmpeg::extractAudio::[ERROR]建立输出流失败\n"); return false; } // 拷贝编码参数,如果需要转码请不要直接拷贝 // 这里只需要做音频的提取,对转码要求不高 ret = avcodec_parameters_copy(out_stream->codecpar, in_codeper); // 将输入流的编码拷贝到输出流 if (ret < 0) { printf("Ffmpeg::extractAudio::[ERROR]拷贝编码失败\n"); return false; } out_stream->codecpar->codec_tag = 0; break; // 拿到音频流就可以直接退出循环,这里我们只需要音频流 } } av_dump_format(pFormatCtx_out, 0, szFilename, 1); // 解复用器,如果没有指定就使用pb if (!(pFormatCtx->flags & AVFMT_NOFILE)) { ret = avio_open(&pFormatCtx_out->pb, szFilename, AVIO_FLAG_WRITE); // 读写 if (ret < 0) { printf("Ffmpeg::extractAudio::[ERROR]创建AVIOContext对象:打开文件失败\n"); return false; } } // 写入媒体文件头部 ret = avformat_write_header(pFormatCtx_out, NULL); if (ret < 0) { printf("Ffmpeg::extractAudio::[ERROR]写入媒体头部失败\n"); return false; } // 逐帧提取音频 AVPacket* pkt = av_packet_alloc(); while (av_read_frame(pFormatCtx, &packet) >=0 ) { AVStream* in_stream = NULL; AVStream* out_stream = NULL; in_stream = pFormatCtx->streams[pkt->stream_index]; out_stream = pFormatCtx_out->streams[pkt->stream_index]; if (packet.stream_index == audio_stream_index) { packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_INF|AV_ROUND_PASS_MINMAX)); packet.dts = packet.pts; packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base); packet.pos = -1; packet.stream_index = 0; // 将包写到输出媒体文件 av_interleaved_write_frame(pFormatCtx_out, &packet); // 减少引用计数,防止造成内存泄漏 av_packet_unref(&packet); } } // 写入尾部信息 av_write_trailer(pFormatCtx_out); // 释放 av_packet_free(&pkt); avio_close(pFormatCtx_out->pb); avformat_close_input(&pFormatCtx); return true; }
3.性能对比
5s |
5min |
30min |
0.087017s |
0.138014s |
0.875926s |
三、视频文件中提取图片
1.FFmpeg
通过提供的API
bool AVInterface::extracPictrue(const char* src, const char* dstDir, int num) { if(NULL == src || NULL == dstDir) { printf("Ffmpeg::extracPictrue[ERROR]::无效参数,请检查文件路径是否正确\n"); return false; } int ret = 0; // 预存原文件路径 const char* src_fileName = src; // 1.获取媒体文件的全局上下文信息 // 1.1 定义 AVFormatContext 容器 AVFormatContext* pFormatCtx = NULL; // AVFormatContext描述了一个媒体文件或者媒体流构成的基本信息 pFormatCtx = avformat_alloc_context(); // 为pFormatCtx申请内存 // 1.2 打开媒体文件,并且读取媒体文件的头信息放入pFormatCtx中 ret = avformat_open_input(&pFormatCtx, src_fileName, NULL, NULL); if(ret < 0) { printf("Ffmpeg::extracPictrue[ERROR]::打开媒体流文件失败\n"); return false; } // 2.探测流信息 // 2.1 探寻文件中是否存在信息流,如果存在则将多媒体文件信息流放到pFormatCtx中 ret = avformat_find_stream_info(pFormatCtx, NULL); if(ret < 0) { printf("Ffmpeg::extracPictrue[ERROR]::文件中不存在信息流\n"); return false; } av_dump_format(pFormatCtx, 0, src_fileName, 0); // 可以打印查看 // 2.2 查找文件信息流中是否存在视频流(这里我们需要提取图片),并获取到视频流在信息流中的索引 int vecdio_stream_index = -1; vecdio_stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if(-1 == vecdio_stream_index) { printf("Ffmpeg::extracPictrue[ERROR]::文件中不存在视频流\n"); return false; } // ----------> 丛林方法1 // 3.找到对应的解码器:音视频文件是压缩之后的,我们要对文件内容进行处理,就必须先解码 // 3.1 定义解码器的容器 AVCodecContext* pCodeCtx = NULL; // AVCodecContext描述编解码器的结构,包含了众多解码器的基本信息 const AVCodec* pCodec = NULL; // AVCodec 存储解码器的信息 pCodeCtx = avcodec_alloc_context3(NULL); // 初始化解码器上下文 // 3.2 查找解码器 AVStream* pStream = pFormatCtx->streams[vecdio_stream_index]; // 在众多解码器找到视频处理的上下文信息 pCodec = avcodec_find_decoder(pStream->codecpar->codec_id); // 根据视频流获取视频解码器的基本信息 if(NULL == pCodec) { printf("未发现视频编码器\n"); return false; } // 初始化解码器上下文 ret = avcodec_parameters_to_context(pCodeCtx, pStream->codecpar); if (ret < 0) { printf("初始化解码器上下文失败\n"); return false; } // 3.3 打开解码器 ret = avcodec_open2(pCodeCtx, pCodec, NULL); if(ret < 0) { printf("无法打开编解码\n"); return false; } AVFrame* pFrame = NULL; pFrame = av_frame_alloc(); if (NULL == pFrame) { printf("av_frame_alloc is error\n"); return false; } int index = 0; AVPacket avpkt; while (av_read_frame(pFormatCtx, &avpkt) >= 0) { if (avpkt.stream_index == vecdio_stream_index) { ret = avcodec_send_packet(pCodeCtx, &avpkt); if (ret < 0) { continue; } while (avcodec_receive_frame(pCodeCtx, pFrame) == 0) { SaveFramePicture(pFrame, dstDir, index); } index++; if (index == num) { break; } } av_packet_unref(&avpkt); } avcodec_close(pCodeCtx); avformat_close_input(&pFormatCtx); return true; } bool AVInterface::SaveFramePicture(AVFrame* pFrame, const char* dstDir, int index) { char szFilename[256] = {0}; snprintf(szFilename, sizeof(szFilename), "%s/ffmpeg-%d.png", dstDir, index); int ret = 0; int width = pFrame->width; int height = pFrame->height; // 1.初始化图片封装格式的结构体 AVCodecContext* pCodeCtx = NULL; AVFormatContext* pFormatCtx = NULL; pFormatCtx = avformat_alloc_context(); // 2.设置封装格式 // MJPEG格式:按照25帧/秒速度使用JPEG算法压缩视频信号,完成动态视频的压缩 --> 视频文件使用MJPEG进行解压 pFormatCtx->oformat = av_guess_format("mjpeg", NULL, NULL); // 用于从已经注册的输出格式中寻找最匹配的输出格式 // 3.创建AVIOContext对象:打开文件 ret = avio_open(&pFormatCtx->pb, szFilename, AVIO_FLAG_READ_WRITE); // 读写方式 if(ret < 0) { printf("avio_open is error"); return false; } // 构建一个新的stream AVStream* pAVStream = NULL; pAVStream = avformat_new_stream(pFormatCtx, 0); if(pAVStream == NULL) { printf("avformat_new_stream\n"); return false; } AVCodecParameters* parameters = NULL; // 编码器参数的结构体 parameters = pAVStream->codecpar; // 设置编码器 mjpeg parameters->codec_id = pFormatCtx->oformat->video_codec; // 视频流 parameters->codec_type = AVMEDIA_TYPE_VIDEO; // 编码类型 //parameters->format = AV_PIX_FMT_BGR24; // 指定图片的显示样式 parameters->format = AV_PIX_FMT_YUVJ420P; // YUV 解压缩显示样式都是YUV parameters->width = pFrame->width; // 指定图片的宽度 parameters->height = pFrame->height; // 显示图片的高度 // 找到相应的解码器 const AVCodec* pCodec = avcodec_find_encoder(pAVStream->codecpar->codec_id); if(NULL == pCodec) { printf("avcodec_find_encoder is error\n"); return false; } // 初始化解码器上下文 pCodeCtx = avcodec_alloc_context3(pCodec); if(NULL == pCodeCtx) { printf("avcodec_alloc_context3 is error\n"); return false; } // 设置解码器的参数 //ret = avcodec_parameters_to_context(pCodeCtx, pAVStream->codecpar); ret = avcodec_parameters_to_context(pCodeCtx, parameters); if(ret < 0) { printf("avcodec_parameters_to_context is error\n"); return false; } AVRational avrational = {1, 25}; pCodeCtx->time_base = avrational; // 打开编解码器 ret = avcodec_open2(pCodeCtx, pCodec, NULL); if(ret < 0) { printf("avcodec_open2 is error\n"); return false; } // 封装格式的头部信息写入 ret = avformat_write_header(pFormatCtx, NULL); if(ret < 0) { printf("avformat_write_header is error\n"); return false; } // 给AVPacket分配足够大的空间 int y_size = width * height; // 分辨率 AVPacket pkt; av_new_packet(&pkt, y_size * 3); // 编码数据 ret = avcodec_send_frame(pCodeCtx, pFrame); if(ret < 0) { printf("avcodec_send_frame is error\n"); return false; } // 得到解码之后的数据 ret = avcodec_receive_packet(pCodeCtx, &pkt); if(ret < 0) { printf("avcodec_receive_packet is error\n"); return false; } ret = av_write_frame(pFormatCtx, &pkt); if(ret < 0) { printf("av_write_frame is error\n"); return false; } av_packet_unref(&pkt); av_write_trailer(pFormatCtx); avcodec_close(pCodeCtx); avio_close(pFormatCtx->pb); avformat_free_context(pFormatCtx); return true; }
3.性能对比
5s |
5min |
30min |
|
10张 |
0.295322s |
0.146283s |
0.151467s |
100张 |
1.263546s |
1.226884s |
1.190490s |
全部 |
2.670444s(170) |
96.951886s(7514) |
119.161211s(10000) |
四、音频文件中提取文字
1.百度智能云语音识别
百度语音目前只支持语音识别,语音合成和语音唤醒,支持pcm wav amr三种格式,时长为60秒以内,价格为完全免费,调用量限制为无限制。 1、离线语音识别 百度离线语音识别目前只支持Android和IOS,Android 平台的一体化离在线语音识别解决方案,以JAR包 + SO库的形式发布。IOS移动设备的离在线语音识别解决方案,以静态库方式提供。 2、在线语音识别 通过API格式调用,Android,iOS,C#,Java,Node,PHP,Python,C++语言,其实是API模式,所有开发语言都支持。 |
1.1百度智能云的优劣
|
1.2 百度智能云安装配置
安装必要的依赖,curl(必须带ssl) jsoncpp openssl #安装libcurl sudo apt-get install libcurl4-openssl-dev #安装jsoncpp sudo apt-get install libjsoncpp-dev 直接使用开发包步骤如下:
|
1.4百度智能云使用示例
用户可以参考如下代码新建一个client: #include "speech.h" // 设置APPID/AK/SK std::string app_id = "XXX"; std::string api_key = "XXX"; std::string secret_key = "XXX"; aip::Speech client(app_id, api_key, secret_key); 在上面代码中,常量APP_ID在百度云控制台中创建,常量API_KEY与SECRET_KEY是在创建完毕应用后,系统分配给用户的,均为字符串,用于标识用户,为访问做签名验证,可在AI服务控制台中的应用列表中查看。 向远程服务上传整段语音进行识别 void asr(aip::Speech client) { // 无可选参数调用接口 std::string file_content; aip::get_file_content("./assets/voice/16k_test.pcm", &file_content); Json::Value result = client.recognize(file_content, "pcm", 16000, aip::null); // 极速版调用函数 // Json::Value result = client.recognize_pro(file_content, "pcm", 16000, aip::null); // 如果需要覆盖或者加入参数 std::map<std::string, std::string> options; options["dev_pid"] = "1537"; Json::Value result = client.recognize(file_content, "pcm", 16000, options); } 返回样例: // 成功返回 { "err_no": 0, "err_msg": "success.", "corpus_no": "15984125203285346378", "sn": "481D633F-73BA-726F-49EF-8659ACCC2F3D", "result": ["北京天气"] } // 失败返回 { "err_no": 2000, "err_msg": "data empty.", "sn": null } |
SpeechRecognition开源离线语音识别
SpeechRecognition,是google出的,专注于语音向文本的转换。wit 和 apiai 提供了一些超出基本语音识别的内置功能,如识别讲话者意图的自然语言处理功能。
SpeechRecognition的优/劣
|
SpeechRecognition安装配置
SpeechRecognition安装配置 pip install SpeechRecognition (pip install -i https://pypi.tuna.tsinghua.edu.cn/simple SpeechRecognition) yum install python3-devel yum install pulseaudio-libs-devel yum install alse-lib-devel pip install packetSphinx 配置中文语音识别数据 下载地址 https://sourceforge.net/projects/cmusphinx/files/Acoustic%20and%20Language%20Models/ 选择 Mandarin->cmusphinx-zh-cn-5.2.tar.gz 安装中文语音包 cd /usr/local/python3.6.8/lib/python3.6/site-packages/speech_recognition/pocketsphinx-data tar zxvf cmusphinx-zh-cn-5.2.tar.gz mv cmusphinx-zh-cn-5.2 zh-cn cd zh-cn mv zh_cn.cd_cont_5000 acoustic-model mv zh_cn.lm.bin language-model.lm.bin mv zh_cn.dic pronounciation-dictionary.dict 配置环境 cd /usr/local/python3.6.8/lib/python3.6/site-packages/speech_recognition/pocketsphinx-data tar zxvf py36asr.tar.gz source ./py36asr/bin/activate |
SpeechRecognition使用示例
语音识别示例: [root@localhost pocketsphinx-data]# pwd /usr/local/python3.6.8/lib/python3.6/site-packages/speech_recognition/pocketsphinx-data [root@localhost pocketsphinx-data]# ls cmusphinx-zh-cn-5.2.tar.gz py36asr test1.py test2.wav zh-cn.tar.gz en-US py36asr.tar.gz test1.wav zh-cn 程序示例: # -*- coding: utf-8 -*- # /usr/bin/python import speech_recognition as sr r = sr.Recognizer() test = sr.AudioFile("test1.wav") with test as source: audio = r.record(source) type(audio) c=r.recognize_sphinx(audio, language='zh-cn') print(c) |
FastASR语音识别
这是一个用C++实现ASR推理的项目,它的依赖很少,安装也很简单,推理速度很快。支持的模型是由Google的Transformer模型中优化而来,数据集是开源。Wennetspeech(1000+小时)或阿里私有数据集(60000+小时),所以识别效果有很好,可以媲美许多商用的ASR软件。
- 流式模型:模拟的输入是语音流,并实时返回语音识别的结果,但是准确率会降低些。
名称 |
来源 |
数据集 |
模型 |
conformer_online |
paddlespeech |
WenetSpeech(1000h) |
conformer_online_wenetspeech-zh-16k |
- 非流式模型:每次识别是以句子为单位,所以实时性会差一些,但是准确率会高一些。
名称 |
来源 |
数据集 |
模型 |
语言 |
paraformer |
阿里达摩院 |
私有数据集(6000h) |
Paraformer-large |
En+zh |
k2_rnnt2 |
kaldi2 |
WenetSpeech(10000h) |
Prouned_transducer_stateless2 |
zh |
Conformer_online |
paddlespeech |
WenetSpeech(10000h) |
Conformer_online_wenetspeech-zh-16k |
zh |
上面提到的这些模型都是基于深度学习框架(paddlepaddle和pytorch)实现的,本身的性能很不错,在个人电脑上运行,也能满足实时性要求(时长为10s的语言,推理视觉小于10s,即可满足实时性)。
FastASR的优/劣
|
FastASR安装配置
- 依赖安装库 libfftw3
sudo apt-get install libfftw3-dev libfftw3-single3 |
- 安装依赖库 libopenblas
sudo apt-get install libopenblas-dev |
- 安装python环境
sudo apt-get install python3 python3-dev |
- 下载最新版的源码
- 编译最新版本的源码
cd FastASR/ mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. make |
- 编译python的whl安装包
cd FastASR python -m build |
- 下载预训练模型
paraformer预训练模型下载 cd ../models/paraformer_cli 1.从modelscope官网下载预训练模型 wget --user-agent="Mozilla/5.0" -c "https://www.modelscope.cn/api/v1/models/damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch/repo?Revision=v1.0.4&FilePath=model.pb"
mv repo\?Revision\=v1.0.4\&FilePath\=model.pb model.pb
../scripts/paraformer_convert.py model.pb
md5sum -b wenet_params.bin K2_rnnt2预训练模型下载 cd ../models/k2_rnnt2_cli 1.从huggingface官网下载预训练模型 2.将用于Python的模型转换为C++的 ../scripts/k2_rnnt2_convert.py pretrained_epoch_10_avg_2.pt 3.通过md5检查是否等于 33a941f3c1a20a5adfb6f18006c11513 md5sum -b wenet_params.bin PaddleSpeech预训练模型下载 1.从PaddleSpeech官网下载预训练模型 2.将压缩包解压wenetspeech目录下 mkdir wenetspeech tar -xzvf asr1_conformer_wenetspeech_ckpt_0.1.1.model.tar.gz -C wenetspeech 3.将用于Python的模型转换为C++的 ../scripts/paddlespeech_convert.py wenetspeech/exp/conformer/checkpoints/wenetspeech.pdparams 4.md5检查是否等于 9cfcf11ee70cb9423528b1f66a87eafd md5sum -b wenet_params.bin 流模式预训练模型下载 cd ../models/paddlespeech_stream
2.将压缩包解压wenetspeech目录下 mkdir wenetspeech tar -xzvf asr1_chunk_conformer_wenetspeech_ckpt_1.0.0a.model.tar.gz -C wenetspeech 3.将用于Python的模型转化为C++的 ../scripts/paddlespeech_convert.py wenetspeech/exp/chunk_conformer/checkpoints/avg_10.pdparams 4.md5检查是否等于 367a285d43442ecfd9c9e5f5e1145b84 md5sum -b wenet_params.bin |
FastASR使用示例
#include <iostream> #include <win_func.h> #include <Audio.h> #include <Model.h> #include <string.h> using namespace std; bool externContext(const char* src, const char* dst) { Audio audio(0); // 申请一个音频处理的对象 audio.loadwav(src); // 加载文件 audio.disp(); // 分析格式 // Model* mm = create_model("/home/chen/FastASR/models/k2_rnnt2_cli", 2); // 创建一个预训练模型 Model* mm = create_model("/home/chen/FastASR/models/paraformer_cli", 3); audio.split(); // 解析文件 float* buff = NULL; // fftw3数据分析 int len = 0; int flag = false; char buf[1024]; // 一行一行的取出内容 FILE* fp = NULL; fp = fopen(dst, "w+"); if(NULL == fp) { printf("打开文件失败\n"); } printf("0.---------------------->\n"); while(audio.fetch(buff, len , flag) > 0) { printf("1.---------------------->\n"); mm->reset(); string msg = mm->forward(buff, len, flag); memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "%s", msg.c_str()); fseek(fp, 0, SEEK_END); fprintf(fp, "%s\n", buf); fflush(fp); printf("2.--------------------->\n"); } printf("3.------------------------>\n"); return true; } int main(void) { externContext("./long.wav", "./Context.txt"); return 0; }
flags:= -I ./include flags+= -L ./lib -lfastasr -lfftw3 -lfftw3f -lblas -lwebrtcvad src_cpp=$(wildcard ./*.cpp) debug: g++ -g $(src_cpp) -omain $(flags) -std=c++11
夜深了,这篇文章中的从之前写的文档里粘贴过来的。有一些地方格式不太好看。见谅...