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 units
formatCtx->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 库来处理多媒体数据,包括视频和音频的解码、播放、保存等操作。