init
函数用于初始化 FFmpeg,包括设置参数、打开输入、初始化视频和音频等。initOption
函数用于设置 FFmpeg 的参数选项。
bool FFmpegThread::init()
{if (url.isEmpty()) {return false;}//判断该摄像机是否能联通if (checkConn && isRtsp) {if (!checkUrl(url, checkTime)) {return false;}}//启动计时QElapsedTimer time;time.start();//初始化参数this->initOption();//初始化输入if (!initInput()) {return false;}//初始化视频if (!initVideo()) {return false;}//初始化音频if (!initAudio()) {return false;}//初始化其他this->initOther();QString useTime = QString::number((float)time.elapsed() / 1000, 'f', 3);qDebug() << TIMEMS << fileFlag << QString("初始化完 -> 用时: %1 秒 地址: %2").arg(useTime).arg(url);return true;
}bool FFmpegThread::initInput()
{//实例化格式处理上下文formatCtx = avformat_alloc_context();//设置超时回调,有些不存在的地址或者网络不好的情况下要卡很久formatCtx->interrupt_callback.callback = AVInterruptCallBackFun;formatCtx->interrupt_callback.opaque = this;//必须要有tryOpen标志位来控制超时回调,由他来控制是否继续阻塞tryOpen = false;tryRead = true;//先判断是否是本地设备(video=设备名字符串),打开的方式不一样QByteArray urlData = url.toUtf8();AVInputFormat *ifmt = nullptr;if (isUsbCamera) {
#if defined(Q_OS_WIN)ifmt = av_find_input_format("dshow");
#elif defined(Q_OS_LINUX)//ifmt = av_find_input_format("v4l2");ifmt = av_find_input_format("video4linux2");
#elif defined(Q_OS_MAC)ifmt = av_find_input_format("avfoundation");
#endif}//设置 avformat_open_input 非阻塞默认阻塞 不推荐这样设置推荐采用回调//formatCtx->flags |= AVFMT_FLAG_NONBLOCK;int result = avformat_open_input(&formatCtx, urlData.data(), ifmt, &options);tryOpen = true;if (result < 0) {qDebug() << TIMEMS << fileFlag << "open input error" << getError(result) << url;emit ffmpegDecodeSignal(fileFlag + " open input error " + getError(result));return false;}//释放设置参数if (options != nullptr) {av_dict_free(&options);}//根据自己项目需要开启下面部分代码加快视频流打开速度
#if 0//接口内部读取的最大数据量,从源文件中读取的最大字节数//默认值5000000导致这里卡很久最耗时,可以调小来加快打开速度formatCtx->probesize = 50000;//从文件中读取的最大时长,单位为 AV_TIME_BASE unitsformatCtx->max_analyze_duration = 5 * AV_TIME_BASE;//内部读取的数据包不放入缓冲区//formatCtx->flags |= AVFMT_FLAG_NOBUFFER;
#endif//获取流信息result = avformat_find_stream_info(formatCtx, nullptr);if (result < 0) {qDebug() << TIMEMS << fileFlag << "find stream info error" << getError(result);emit ffmpegDecodeSignal(fileFlag + " find stream info error " + getError(result));return false;}return true;
}
run
函数是线程的运行函数,用于循环读取音视频数据包,并进行解码和播放。
void FFmpegThread::run()
{//记住开始解码的时间用于用视频同步startTime = av_gettime();while (!stopped) {//根据标志位执行初始化操作if (isPlay) {if (init()) {//这里也需要更新下最后的时间lastTime = QDateTime::currentDateTime();initSave();//初始化完成变量放在这里,绘制那边判断这个变量是否完成才需要开始绘制if (videoIndex >= 0) {isInit = true;}emit receivePlayStart();} else {emit receivePlayError();break;}isPlay = false;continue;}//处理暂停 本地文件才会执行到这里 视频流的暂停在其他地方处理if (isPause) {//这里需要假设正常,暂停期间继续更新时间lastTime = QDateTime::currentDateTime();msleep(1);continue;}//QMutexLocker locker(&mutex);//解码队列中帧数过多暂停读取 下面这两个值可以自行调整 表示缓存的大小if (videoSync->getPacketCount() >= 100 || audioSync->getPacketCount() >= 100) {msleep(1);continue;}//必须要有tryRead标志位来控制超时回调,由他来控制是否继续阻塞tryRead = false;//下面还有个可以改进的地方就是如果是视频流暂停情况下只要保证 av_read_frame 一直读取就行无需解码处理frameFinish = av_read_frame(formatCtx, packet);//qDebug() << TIMEMS << fileFlag << "av_read_frame" << frameFinish;if (frameFinish >= 0) {tryRead = true;//更新最后的解码时间 错误计数清零errorCount = 0;lastTime = QDateTime::currentDateTime();//判断当前包是视频还是音频int index = packet->stream_index;if (index == videoIndex) {//qDebug() << TIMEMS << fileFlag << "videoPts" << qint64(getPtsTime(formatCtx, packet) / 1000) << packet->pts << packet->dts;decodeVideo(packet);} else if (index == audioIndex) {//qDebug() << TIMEMS << fileFlag << "audioPts" << qint64(getPtsTime(formatCtx, packet) / 1000) << packet->pts << packet->dts;decodeAudio(packet);}} else if (!isRtsp) {//如果不是视频流则说明是视频文件播放完毕if (frameFinish == AVERROR_EOF) {//当同步队列中的数量为0才需要跳出 表示解码处理完成if (videoSync->getPacketCount() == 0 && audioSync->getPacketCount() == 0) {//循环播放则重新设置播放位置,在这里执行的代码可以做到无缝切换循环播放if (playRepeat) {this->position = 0;videoSync->reset();audioSync->reset();videoSync->start();audioSync->start();QMetaObject::invokeMethod(this, "setPosition", Q_ARG(qint64, position));qDebug() << TIMEMS << fileFlag << "repeat" << url;} else {break;}}}} else {//下面这种情况在摄像机掉线后出现,如果想要快速识别这里直接break即可//一般3秒钟才会执行一次错误累加errorCount++;//qDebug() << TIMEMS << fileFlag << "errorCount" << errorCount << url;if (errorCount >= 3) {errorCount = 0;break;}}free(packet);msleep(2);}QMetaObject::invokeMethod(this, "stopSave");//线程结束后释放资源msleep(100);free();freeAudioDevice();emit receivePlayFinsh();//qDebug() << TIMEMS << fileFlag << "stop ffmpeg thread" << url;
}
以上是部分代码,这个类的主要目的是使用 FFmpeg 库来处理多媒体数据,包括视频和音频的解码、播放、保存等操作。