假期之不务正业——Qt+FFmpeg+百度api进行视频的语音识别

  • 一、前言
  • 二、FFmpeg进行音频提取和重采样
  • 三、对音频分段
  • 四、百度api调用
  • 五、Qt编程的一些补充
  • 六、结语

一、前言

现在语音识别技术逐渐发展,先有siri开个好头,现在有各种小度小爱什么的轮番上阵。王者荣耀有语音识别以后,祖安起来也省事多了。我看一些视频教程的时候,对一些讲的不错的,也有记笔记的习惯。可是每次都是把视频暂停,然后一句一句敲出word,说实话,也没见学习效果有多好,反而效率变得低到不行。想来想去,咱也不能一直停留在这么笨比的方式,总是想整点活。

其实网上就有一些提取字幕的、或是语音识别的应用,应该效果也不错(我没试),但是要钱(emmmmm)。所以暂时先放弃这个方案,而且如果自己做一个那不是快乐加倍?于是利用假期时间,自己找了一些资料借(chao)鉴(xi)了一下,也是算是自己从零开始做的垃圾。

先放一下目前做到的:



我主要选择了4个B站的视频来测试运行结果,顺便一提,B站用手机端下载视频后,会在缓存文件里发现audio.m4s和vedio.m4s。实际上,FFmpeg可以直接打开m4s格式,因此如果仅仅是为了对音频进行处理,不需要将两个文件合流为一个(合流的方法也很简单,尤其是使用FFmpeg,可以直接百度)。

我选择的4个视频分别是冰冰vlog、卢本伟17张牌名场面、小潮院长的不要做挑战和吴恩达老师的机器学习课程,链接放在下文,我这里就夹带私货安利一波。下面是识别结果:



简单复盘一下:识别结果也算还可以,与英文相比,中文能够带上标点符号看起来更利落一些。显然,语速放慢,说话更标准时,识别效果更好(这不是废话吗)。可以看到小潮的不要做挑战的前面正经讲游戏规则时,识别结果还能接受,到后面整活了,识别结果驴唇不对马嘴。对于语速中规中矩的视频(尤其对于目的:视频教程),能有一些帮助;但如果是小视频(尤其节奏比较快的),那还是算了吧。

总体思路就是:Qt做个外壳,FFmpeg提取视频里的音频,百度api进行语音识别。由于百度开放的免费接口要求时长在1分钟以内,所以对于超过一分钟时长的音频,需要进行分段(顺便一提,免费接口使用量是中文普通话5w次,英文2w次)。下面对于各个部分的内容和遇到的(包括未处理完的)问题简单做一下记录。

以下是本实现主要参考资料的相关链接:
1、提供FFmpeg相关操作流程:
《使用 FFmpeg 进行音视频操作》,这个CSDN博客介绍了FFmpeg的主要模块、音视频解码与重采样等内容,主要都是文字介绍,具体代码实现也有一部分,有一定的参考价值(后面的记录仅写一些我的工作和问题吧,这个博客的内容不会转载的)。放下链接:
https://gitchat.csdn.net/activity/5d08d7d44ea36e699ecac739
2、提供百度API相关操作流程:
《Qt语音识别 | 百度语音识别应用》,这个B站视频介绍百度API的接口、使用Qt来调用百度API的方法,我的相关操作全部参考这个视频(因此后面的记录里代码部分不会太多,引用也经过老师同意),有兴趣的直接看视频吧。放下链接:
https://www.bilibili.com/video/BV19K411V79h

以下是上面效果展示的原视频链接:
1、【冰冰vlog.001】带大家看看每个冬天我必去的地方
https://www.bilibili.com/video/BV1vy4y1i7bS
2、【名场面】17张牌你能秒我?你能秒杀我?你今天17张牌把卢本伟秒了,我当场就把这个电脑屏幕吃掉!
https://www.bilibili.com/video/BV1W4411r7ue
3、不要“做”挑战 ?
https://www.bilibili.com/video/BV1x7411Z7VA
4、[中英字幕]吴恩达机器学习系列课程
https://www.bilibili.com/video/BV164411b7dx

二、FFmpeg进行音频提取和重采样

关于FFmpeg的介绍、使用,可以直接看前言的链接,或者找其他教程,这里也直接梳理一下我们需要做的事情和以及整个过程:
1.对于视频文件,需要解封装,即分离出音频流或者视频流或者其他乱七八糟的东西。得到音频流参数,如声道数、采样率、采样格式等等。
2.解封装后的音频流,再进行解码,得到音频的实际采样数据。
3.设置重采样参数,分配存储重采样的数据空间。对于重采样参数,需要配合百度API的要求:单声道、采样率16000Hz、16bit量化。
4.读取原数据,将重采样后得到的数据,并将数据写入文件,建议直接pcm文件,简单粗暴。
5.释放之前申请的资源。

对于这部分,我们可以考虑封装成一个类ExtractAudio(请不要吐槽我的命名品味,真不会),方便调用和后续的查看,最开始调试时我就是直接全写在一个函数里面的,省事是省事,但是太长了会看得累。以下是代码(.cpp)部分:

void ExtractAudio::init()
{//初始化参数in_nb_samples = 1024; //输入采样点数out_channel_layout = AV_CH_LAYOUT_MONO; //输出格式(声道数)out_sample_rate = SAMPLE_RATE; //输出采样率out_sample_fmt = AV_SAMPLE_FMT_S16; //输出样本格式
}//打开文件函数,返回值为解封装上下文
AVFormatContext *ExtractAudio::open(QString inpath)
{av_register_all();//初始化封装库AVDictionary *opts = NULL;//参数设置AVFormatContext *format = avformat_alloc_context();//解封装上下文//QString转换为char数组QByteArray ba = inpath.toLocal8Bit();char* cpath = ba.data();//打开视频文件,参数3:0表示自动选择解封器,参数4:参数设置(比如rtsp的延时时间)int re = avformat_open_input(&format, (const char*)cpath, 0, &opts);if (re != 0)//打开失败return NULL;elsereturn format;
}//解码函数,返回值为解码器上下文
AVCodecContext *ExtractAudio::decodec(AVFormatContext *format)
{//获取流信息,不是所有的格式都需要调用//但是即便头已经获取过,这里再获取也没有问题//所以原则上每次都获取一下int re = avformat_find_stream_info(format, 0);//获取流信息if (re < 0)return NULL;//使用遍历的方法获取音视频流信息for (int i = 0; i < format->nb_streams; i++){AVStream *as = format->streams[i];//音频if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){audioStream = i;break;}}//音频解码器打开AVCodec *acodec = avcodec_find_decoder(format->streams[audioStream]->codecpar->codec_id); //找到音频解码器if (!acodec) //没有找到音频解码器return false;AVCodecContext *avctx = avcodec_alloc_context3(acodec); //创建解码器上下文avcodec_parameters_to_context(avctx, format->streams[audioStream]->codecpar); //配置解码器上下文参数avctx->thread_count = 8; //解码线程数改为8re = avcodec_open2(avctx, 0, 0); //打开解码器上下文if (re != 0) //打开解码器失败return NULL;return avctx;
}//音频重采样初始化函数,返回值为音频重采样上下文
SwrContext *ExtractAudio::initswr(AVCodecContext *avctx, uint8_t **out_data)
{//设置音频重采样SwrContext *swr = swr_alloc();in_channel_layout = avctx->channel_layout;in_sample_rate = avctx->sample_rate;in_sample_fmt = avctx->sample_fmt;av_opt_set_int(swr, "in_channel_layout", in_channel_layout, 0);av_opt_set_int(swr, "out_channel_layout", out_channel_layout, 0);av_opt_set_int(swr, "in_sample_rate", in_sample_rate, 0);av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);av_opt_set_sample_fmt(swr, "in_sample_fmt", in_sample_fmt, 0);av_opt_set_sample_fmt(swr, "out_sample_fmt", out_sample_fmt, 0);swr_init(swr);if (!swr_is_initialized(swr))return NULL;//计算转换样本的数量:避免缓存//确保输出缓冲区至少包含所有转换后的输入样本out_nb_samples = av_rescale_rnd(in_nb_samples, out_sample_rate, in_sample_rate, AV_ROUND_UP);//缓冲区将直接写入原始音频文件,无需对齐out_nb_channels = av_get_channel_layout_nb_channels(out_channel_layout);int re = av_samples_alloc_array_and_samples(&out_data, &out_linesize, out_nb_channels,out_nb_samples, out_sample_fmt, 0);if (re < 0)return NULL;return swr;
}//音频重采样函数,返回值为输出缓冲区的字节数
//返回值为0时,未找到音频流或暂无音频流,可继续执行函数
//返回值为-1时,重采样失败,应中断
int ExtractAudio::resample(AVFormatContext *format, AVCodecContext *avctx, SwrContext *swr, uint8_t **out_data, AVFrame *frame, AVPacket *pkt)
{if (pkt->stream_index != audioStream) //判断是否为音频流return 0;//解码一帧音频int gotFrame;if (avcodec_decode_audio4(avctx, frame, &gotFrame, pkt) < 0)return -1;if (!gotFrame)return 0;//重采样int frame_count = swr_convert(swr,out_data, out_nb_samples, //输出(const uint8_t **)frame->data, in_nb_samples  //输入);if (frame_count < 0)return -1;out_bufsize = av_samples_get_buffer_size(&out_linesize, out_nb_channels, frame_count, out_sample_fmt, 1);av_packet_unref(pkt);//释放,引用计数-1,为0释放空间av_frame_unref(frame);return out_bufsize;
}// 释放空间函数
void ExtractAudio::clear(AVFormatContext *format, AVCodecContext *avctx, SwrContext *swr, AVFrame *frame, AVPacket *pkt)
{//结束,释放空间avformat_close_input(&format);avcodec_close(avctx);swr_free(&swr);av_frame_free(&frame);av_packet_free(&pkt);av_free(frame);av_free(pkt);
}

但是这里虽然代码上释放了,占用空间并没有释放。我自己测试如果打开了一个2G的视频,即便将整个过程都跑完,引用计数也减了,free函数也用了,2G内存还是占着,吐血。所以每次感觉视频大小差不多了,就可以把应用关了重开吧。

三、对音频分段

得到重采样完的数据之后,就可以进行分段处理了。对于短语音识别,时长不能超过1分钟,我这里采用的方法就是,在从每段音频第30s处开始,一直到第60s前,计算1s以内采样值(绝对值)之和,和最小的地方,是我认为这个人声说话的停顿处。有几点补充就是,一是采样率已经默认好是16000Hz;二是每两次求和间的步进,我暂时默认为是0.01s,比如求完了第30s—第31s的和,下一次就求30.01s—31.01s的和。当然这个步进是可以进行变化的,但是个人认为没有必要使步进太小,计算次数变多后很慢(我做过步进是一个采样点的尝试,速度非常非常的慢)。

当然这个方法肯定并不是最优的,对于有BGM的视频来说,可能人不在说话,背景音乐还是有的,从一句话中间给掐断的可能性不是没有。另一个是参数的设置,这里面有很多参数是需要根据视频的情况的调整的,包括比如上面说的从第30s开始,可以换成别的数字;再比如计算1s以内的采样值之和,如果视频的节奏比较快(像小潮的一些视频)或者说话人语速感人,也可以调整;或者是步进等其他参数。但是我觉得我这里设置的参数还算中规中矩,也可以不变。对于这一部分,我们封装为SeparatePCM类。以下是代码(.cpp)部分:

#include "SeparatePCM.h"#include <qdir.h>#define SAMPLE_RATE 16000SeparatePCM::SeparatePCM()
{//初始化//创建一个新缓冲文件夹,用于保存分段后的每一段音频数据QDir *folder = new QDir;folderStr = "D:\\temp\\temp\\";bool exist = folder->exists(folderStr);if (!exist){folder->mkdir(folderStr);}delete folder;//音频处理相关系数初始化sample_rate = SAMPLE_RATE;sample_amount = 60 * sample_rate; //60s内的样点总数start = 0; //每次分段时的第0s的位置position = 0; //当前位置best_position = 0; //判断的最佳静音段位置now_sum = 0; //初始分段的采样点值之和number = 1; //初始分段序号//下面的参数可以根据实际情况进行调整step = 0.01 * sample_rate; //步进,这里设定为0.01s,可以根据实际情况调整threshold_len_silence = 1 * sample_rate; //判断为静音段的默认时长,这里设定为1s,可以根据实际情况调整start_position = (long)sample_amount / 6 * 3; //开始分段的位置,这里设定为第30s,可以根据实际情况调整
}SeparatePCM::~SeparatePCM()
{}//打开文件函数,返回打开文件是否成功
bool SeparatePCM::open(QString inpath)
{filePath = inpath;QByteArray ba = filePath.toLocal8Bit();char* path = ba.data();//获取文件的指针FILE *file = fopen((const char*)path, "rb");if (!file)return false;//把指针移动到文件的结尾 ,获取文件长度fseek(file, 0, SEEK_END);//获取文件长度fileLength = ftell(file);//关闭文件fclose(file);return true;
}//音频文件分段处理函数
void SeparatePCM::execute()
{// 打开文件QByteArray ba = filePath.toLocal8Bit();char* path = ba.data();FILE *file = fopen((const char*)path, "rb");//定义数组长度long bufferSize = fileLength / 2;//判断音频时长是否够60sif (bufferSize < sample_amount){//音频文件时长不足60s,不需要分段outpath = folderStr + pcmStr.arg(1);QFile::copy(filePath, outpath);fclose(file);return;}//设置读取文件存储区short *fileBuffer = new short[bufferSize];//读文件fread(fileBuffer, sizeof(short), bufferSize, file);//对超过60s音频文件进行分段short max_value = 0; //音频文件采样值的最大值(绝对值)for (long i = 0; i < bufferSize; i++){if (abs(fileBuffer[i]) > max_value)max_value = abs(fileBuffer[i]);}//记录分段中最小的采样点值之和,初始值设定大一些方便后续更新min_sum = (long)threshold_len_silence * max_value; //分段数据缓冲区short *cutfileBuffer = new short[sample_amount];//循环执行音频分段,直到剩一段时长<60swhile (true){//从分段的位置开始,间隔步长,遍历寻找分段点for (position = start_position + start; position < (long)sample_amount + start - 1; position += step){//计算默认静音时长下的采样值的和for (int i = 0; i < threshold_len_silence; i++){now_sum = now_sum + (long)abs(fileBuffer[position - i]);}//判断是否最小if (now_sum < min_sum){min_sum = now_sum;//best_position = position - threshold_len_silence / 2;best_position = position - (long)threshold_len_silence / 2;}now_sum = 0;}//复制数据并把结果写入文件copyData_and_writeFile(fileBuffer, cutfileBuffer, best_position - start + 1);//判断剩下的数据是否还需要分段(若剩下的数据不足60s,直接导出即可)start = best_position + 1;number++;if (start > bufferSize - sample_amount){//复制数据并把结果写入文件copyData_and_writeFile(fileBuffer, cutfileBuffer, bufferSize - start + 1);break;}//为下次分段初始化now_sum = 0;min_sum = (long)threshold_len_silence * max_value;}delete[] cutfileBuffer;delete[] fileBuffer;fclose(file);//删除提取的音频文件QFile fileTemp(filePath);fileTemp.remove();fileTemp.close();
}//复制数据并将其写入文件
//参数:文件存储区指针、分段数据缓冲区指针、数据长度
void SeparatePCM::copyData_and_writeFile(short *fileBuffer, short *cutfileBuffer, int len_cut)
{short *pfile = NULL; //设置原文件读取指针//复制数据pfile = fileBuffer + start;memcpy(cutfileBuffer, pfile, len_cut * 2);//把结果写入文件outpath = folderStr + pcmStr.arg(number);QByteArray qba = outpath.toLocal8Bit();char *cpath = qba.data();FILE *cfile = fopen((const char*)cpath, "wb");fwrite(cutfileBuffer, sizeof(short), len_cut, cfile);fclose(cfile);
}

四、百度api调用

这里也不再多说,请全部参考上文的B站视频吧,代码也不放了,基本是一模一样的。唯一的区别是我加上了“中文”或者“英文”的判断,在url里改变pid=1537或者1737。在这基础上,封装成了一个WriteText类。以下是代码(.cpp)部分:

#include "WriteText.h"
#include "Speech.h"
#include <qdir.h>
#include <qfile.h>
#include <qiodevice.h>WriteText::WriteText()
{}WriteText::~WriteText()
{}void WriteText::execute(QString fileName, int id)
{QFile file(fileName);file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append);//开始识别//可以获取文件夹路径下的所有文件信息QStringList filter;//文件筛选,可以置为空,获取所有文件信息filter << QString("*.pcm");//找到分段后的缓冲文件夹QString folderStr = "D:\\temp\\temp\\";//获取文件夹信息,并初始化需要识别的文件QDir dir(folderStr);dir.setNameFilters(filter);QFileInfoList fileInfoList = dir.entryInfoList(filter);int dir_count = fileInfoList.count();QString pcmFileName("%1.pcm");QString fullFileName;for (int i = 0; i < dir_count; i++){//遍历文件夹内的所有文件fullFileName = folderStr + pcmFileName.arg(i + 1);//利用百度api进行音频识别Speech m_speech;QString str = m_speech.speechIdentify(fullFileName, id);//将结果写入文件中QTextStream txtStream(&file);txtStream << str << "\n";//删除缓存的音频分段文件QFile fileTemp(fullFileName);fileTemp.remove();fileTemp.close();}file.close();//删除保存分段音频的缓存文件夹dir.removeRecursively();
}

另外在提醒一点就是,调用api之前,一定要先确保自己的免费额度已经领取(如下图),否则调用api失败的同时貌似还占用了次数(我也不太清楚),反正就是算是个坑吧,我就找了半天错误,查了好久才发现是这里出错了QAQ,错误码3304。

五、Qt编程的一些补充

1、Qt在打开文件时,可能面对一些带有中文的字符串,我的方法是在需要支持中文的cpp最开始进行以下声明:

//设置UTF-8编码以支持中文
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
# pragma execution_character_set("utf-8")
#endif

然后在构造函数里添加:

//设置中文编码
QTextCodec *codec = QTextCodec::codecForName("GBK");
QTextCodec::setCodecForLocale(codec);

即可。
当然GBK是windows系统下的,如果跨平台的话还需要找其他编码。

2、整个流程执行下来速度不算慢,但是也需要等待,这个时候肯定是要把运算的流程放入运算线程里面防止界面卡死。创建自定义线程类MyThread,继承于QThread,重写run函数,并定义bool值判断线程结束与否。先放代码:
MyThread.h:

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QFileInfo>
#include <QMessageBox>
#include <QTextCodec>
#include <QFile>#include "ExtractAudio.h"
#include "SeparatePCM.h"
#include "WriteText.h"class QString;class MyThread : public QThread
{Q_OBJECT
public:MyThread();void setMessage(const QStringList &message);void setLanguage(int id);void stop();protected:void run();void extracrAudio(QString strInPath, QString strOutPath); //提取音频并重采样QString separatePCM(QString strInPath); //音频分段void writeText(QString strInPath); //语音识别并将结果写入txtprivate:QStringList str_path_list; //待处理的视频文件列表int languageId; //传入语种idvolatile bool m_Stopped;signals:void updateProgress(int);void updateLabel(QString);
};#endif // MYTHREAD_H

MyThread.cpp:

#include "mythread.h"
#include <iostream>
using namespace std;//设置UTF-8编码以支持中文
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
# pragma execution_character_set("utf-8")
#endifMyThread::MyThread()
{m_Stopped = false;//设置中文编码QTextCodec *codec = QTextCodec::codecForName("GBK");QTextCodec::setCodecForLocale(codec);
}void MyThread::setMessage(const QStringList &message)
{str_path_list = message;
}void MyThread::setLanguage(int id)
{languageId = id;
}void MyThread::stop()
{m_Stopped = true;
}void MyThread::run()
{while (!m_Stopped){//doSomethingQString strShowLabel;for (int i = 0; i < str_path_list.size(); i++){QString inPath = str_path_list[i]; //单个输入文件路径QFileInfo fileInfo = QFileInfo(inPath); //获取输入文件信息QString file_name = fileInfo.fileName(); //输入文件名QString fileSuffix = fileInfo.suffix(); //输入文件后缀strShowLabel = "正在处理:" + file_name; emit updateLabel(strShowLabel);QString outPcmName = file_name.replace(fileSuffix, "pcm"); //输出pcm文件名QString outPcmPath = "D:\\temp\\" + outPcmName; //输出pcm路径QString outTextName = file_name.replace("pcm", "txt"); //输出txt文件名QString outTextPath = "D:\\temp\\" + outTextName; //输出txt路径//下面这一段是处理步骤extracrAudio(inPath, outPcmPath); //提取音频并重采样QString temppath = separatePCM(outPcmPath); //音频分段,并获取缓冲文件夹writeText(outTextPath); //音频识别,并将结果写入txt中cout << endl;int v = 100 * (i + 1) / str_path_list.size();emit updateProgress(v);}str_path_list.clear();strShowLabel = tr("处理结束!");emit updateLabel(strShowLabel);}     m_Stopped = false;
}//提取音频并重采样
void MyThread::extracrAudio(QString strInPath, QString strOutPath)
{//申请输出空间,先按照最大需求量申请uint8_t **out_data;int GroupSize = 1; //外层sizeint innerSize = 60 * 16000 * 2; //内层size,60s*16000Hz*2Bytes*1channelint maxbufferSize = 0;out_data = (uint8_t**)malloc(sizeof(uint8_t*)*GroupSize);for (int i = 0; i < GroupSize; i++){out_data[i] = (uint8_t*)malloc(sizeof(uint8_t)*innerSize);}ExtractAudio ea; //创建对象ea.init(); //初始化AVFormatContext *format = ea.open(strInPath); //打开文件if (!format){QMessageBox::warning(NULL, "提示", "打开文件失败!");return;}cout << "Open file successed!" << endl;AVCodecContext *avctx = ea.decodec(format);; //解码if (!avctx){QMessageBox::about(NULL, "提示", "解码失败!");return;}cout << "Decodec successed!" << endl;SwrContext *swr = ea.initswr(avctx, out_data); //音频重采样初始化if (!swr){QMessageBox::about(NULL, "提示", "音频重采样初始化失败!");return;}cout << "Initswr successed!" << endl;AVFrame *frame = av_frame_alloc(); //malloc AVFrame 并初始化AVPacket *pkt = av_packet_alloc(); //malloc AVPacket 并初始化int bufferSize = 0; //输出缓冲区的字节数//创建写出的pcm文件QFile outFile(strOutPath);outFile.open(QIODevice::WriteOnly);//读取数据while (av_read_frame(format, pkt) >= 0){// 重采样并获取输出字节数bufferSize = ea.resample(format, avctx, swr, out_data, frame, pkt);if (bufferSize > 0) //有重采样的数据,写入文件中outFile.write((const char*)out_data[0], bufferSize);else if (bufferSize == 0) //暂无重采样的数据,继续执行continue;else //重采样出现错误,停止执行{QMessageBox::about(NULL, "提示", "音频重采样失败!");break;}}outFile.close();ea.clear(format, avctx, swr, frame, pkt); //释放空间cout << "ExtracrAudio Finish!" << endl;//释放空间for (int i = 0; i < GroupSize; i++){free(out_data[i]);}free(out_data);
}//音频分段
QString MyThread::separatePCM(QString strInPath)
{SeparatePCM sp; //创建对象bool flag = sp.open(strInPath); //打开文件if (!flag){QMessageBox::warning(NULL, "提示", "打开音频文件失败!");return NULL;}sp.execute(); //音频分段return sp.folderStr;cout << "SeparatePCM Finish!" << endl;
}//语音识别并将结果写入txt
void MyThread::writeText(QString strInPath)
{WriteText wt; //创建对象wt.execute(strInPath, languageId); //执行cout << "WriteText Finish!" << endl;
}

线程函数里,两个信号void updateProgress(int)和void updateLabel(QString)用来更新界面的进度条和便签。在MyThread里面发送信号后,在界面连接信号和槽:

connect(&m_thread, SIGNAL(updateProgress(int)), this, SLOT(updateProgress(int)));
connect(&m_thread, SIGNAL(updateLabel(QString)), this, SLOT(updateLabel(QString)));

其中信号是MyThread的信号(signals),槽是界面的槽(slots)。
而如果界面向线程发送参数的话,直接调用线程里的函数。例如在界面中有两个单选按钮来提供选择“中文”或是“英文”的功能,并且将这两个合并成一个组合:

// 设置单选按钮组合
groupButton = new QButtonGroup(this);
groupButton->addButton(ui.rbtn_Chinese, 0);
groupButton->addButton(ui.rbtn_English, 1);
ui.rbtn_Chinese->setChecked(true); //默认选择中文

在点击开始按钮时,我们就需要判断选择了哪个单选按钮,并把结果传递给运算线程:

int id = groupButton->checkedId();
m_thread.setLanguage(id);

上述的void setLanguage(int id)是线程类里的一个公共函数,直接在界面里面调用即可。把界面所确定的文件列表传递给线程类也是同理。

六、结语

内容差不多就这些了,也都是一些很新手的东西,非常欢迎大佬们给出一些好的建议(尤其是FFmpeg释放内存那里,能连带解决方案就更好了),demo就不放出来了,弄了一个半成品再放出来就觉得很惭愧。

计划以后每年都利用各种假期的时间集合起来,做个小东西,同时更新一下这个系列,做什么方向就看自己的脑洞和心情,反正是假期不务正业时间,如果有好的想法也欢迎一起学习一起做。

假期之不务正业—— Qt+FFmpeg+百度api进行视频的语音识别相关推荐

  1. 利用百度API进行视频翻译制作

    前言 在某次翻墙学习算法的时候,发现了一个学习视频讲的挺不错的,但是没有字幕而且语音是英文,虽然YouTube有翻译的强大功能,现在市场上也有许多软件可以依据视频提取语音文本,但是自己还是想试一试能不 ...

  2. QT调用百度语音REST API实现语音合成

    QT调用百度语音REST API实现语音合成 1.首先点击点击链接http://yuyin.baidu.com/docs/tts 点击access_token,获取access_token,里面有详细 ...

  3. QT 调用 百度翻译API 写的在线翻译程序

    由于作者第一次写博客,并且水平有限,有不足之处希望大家指出. 所以废话不多说直接上核心代码边说边看. 参考文档百度API翻译文档 如果你没有账号请你先去注册一个,注册位置:http://api.fan ...

  4. Qt+百度API实现人脸对比寻找明星脸

    一.要求 1.使用百度人脸识别库 2.识别对比图片,获取图片相似度 3.显示最相似的明星照片.显示本人照片 4.调用摄像头拍照.比对查找最相似的明星脸 5.录入明星照片.查看照片列表.修改.删除照片 ...

  5. Qt调用百度翻译api

    参考Gitee某工程 一.代码 //百度翻译 void CBaiduTranslater::translate(const QString &src, const QString from, ...

  6. QT接入百度翻译api实现翻译

    思路:将自己需要翻译的内容添加到百度翻译的api里面,然后通过get方法发送请求,异步接收返回的json格式数据并解析,然后将解析后的内容显示到界面. 步骤: 1.要实现该功能首先需要百度api的相关 ...

  7. python aipspeech_Python调用百度API实现语音识别(二)

    咪哥杂谈 本篇阅读时间约为 5 分钟. 1 前言 上一篇文章里,大致介绍了百度官方 api 的一些前置准备工作. 想回顾的同学,可以看完本篇在下面找到历史链接. 今天就来上手实战编码,体验一下代码实现 ...

  8. mac下载的api文档怎么_Python调用百度API实现语音识别(二)

    Python调用百度API实现语音识别(二) 前言 上一篇文章里,大致介绍了百度官方 api 的一些前置准备工作. 想回顾的同学,可以看完本篇在下面找到历史链接. 今天就来上手实战编码,体验一下代码实 ...

  9. 简单几步实现网络音乐播放器(Qt版百度FM)

                                  百度FM音乐Qt版本 很久之前写的软件了,一直没有总结,也懒得继续开发了,这里简单总结一下,也算是对自己的努力一个交代吧. 先来个ubunt ...

最新文章

  1. SHELL脚本 基础一
  2. POJ 2778 DNA Sequence (自动机DP+矩阵快速幂)
  3. 最佳适应算法模拟内存分配
  4. 带有Spring Boot和Spring Cloud的Java微服务
  5. 程序员作图工具和技巧,你 get 了么?
  6. 利用pandas对一列/多列进行数据区间筛选
  7. 【书籍阅读】-人在回路机器学习 Human-in-the-Loop Machine Learning(一)
  8. IDEA新手使用教程(详解)
  9. 二阶系统阶跃响应实验__ 二阶系统的阶跃响应实验报告
  10. matlab中提取裂缝图像,灰度图像中裂缝自动识别和检测方法与流程
  11. mysql汽车网站数据库设计_基于数据库和JAVA的网上汽车租赁管理系统的设计(MySQL)...
  12. PostgreSQL 11.2 手册 (中文版) PostgreSQL中文社区
  13. 计算机编码知识,ASCII编码,GBK,Unicode,UTF-8编码详细介绍
  14. #Unity _ 简体转繁体
  15. 招聘中使用的奇葩心理分析
  16. 计算机视觉 python 解图片数独题
  17. 36 个助你成为专家需要掌握的 JavaScript 概念
  18. 手机有软件测试网络通不通,怎样使用ping命令测试网络通不通
  19. 计算机管理无法打开权限不足,win10管理员权限不足怎么设置_win10管理员权限不足如何解决...
  20. tx2使用teamviewer远程桌面访问

热门文章

  1. 工厂模式 五种写法总结
  2. 计算机组成原理扩展指令CLC实验报告,计算机组成原理实验报告.doc
  3. 无线通信中比bit更小的“”数据单位”码片(也叫码元 ,chip) 什么是符号速率
  4. 任意多边形的面积(C语言)
  5. ArrayList 集合底层实现原理解析
  6. 【342期】SpringBoot + Redis 布隆过滤器防恶意流量击穿缓存的正确姿势!
  7. Docker知识总结 (六) Docker网络
  8. 数据库勒索病毒故障处理
  9. TVS二极管和稳压二极管应用有什么不同点
  10. C - 1111gal password