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 庫來處理多媒體數據,包括視頻和音頻的解碼、播放、保存等操作。