亲宝软件园·资讯

展开

Qt利用ffmpeg实现音视频同步

feiyangqingyun 人气:0

一、前言

用ffmpeg来做音视频同步,个人认为这个是ffmpeg基础处理中最难的一个,无数人就卡在这里,怎么也不准,本人也是尝试过网上各种demo,基本上都是渣渣,要么仅仅支持极其少量的视频文件比如收到的数据包是一帧视频一帧音频的,要么根本没法同步歪七八糟的,要么进度跳过去直接蹦蹦蹦崩溃的,其实最完美的音视频同步处理demo就是ffplay,我亲测过几十种各种各样的音视频本地文件,数十种视频流文件,都是非常完美,当然啦这是亲生的啦,不完美还玩个屁。

如果仅仅是播放视频流(不带音频流),可能不需要音视频同步,所以最开始只做rtsp视频流播放的时候根本没有考虑同步的问题,因为没遇到也不需要,等到后期发现各种rtmp、http、m3u8这种视频流的时候,问题大了去了,他是hls格式的视频流文件一次性过来的,一个个小视频文件过来的,如果没有同步的话,意味着突然之间刷刷刷的图片过去很多,下一次来的又是刷刷的,这就需要自己计算同步了,上次接收到的数据包放入队列,到了需要显示的时候就显示。

常用的音视频同步方法:

通过fps来控制,fps表示一秒钟播放多少帧,比如25帧,可以自行计算一帧解码用掉的时间,一帧占用(1000/25=40毫秒),通过延时来处理,这其实是最渣渣的办法。 记住开始解码的时间startTime,通过av_rescale_q计算pts时间,两者的差值就是需要延时的时间,调用av_usleep来延时,这种只有部分文件正常,很多时候不正常。 音频同步到视频,视频时钟作为主时钟,没试过,网上很多人说这个办法不好。 视频同步到音频,音频时钟作为主时钟,没试过,据说大部分人采用的此办法。 音视频同步到外部时钟,外部时钟作为主时钟,最终采用的办法,容易理解互不干扰,各自按照外部时钟去同步自己。 ffplay自身内置了三种同步策略,可以通过参数来控制采用何种策略,默认是视频同步到音频。

二、效果图

三、体验地址

国内站点:https://gitee.com/feiyangqingyun

国际站点:https://github.com/feiyangqingyun

体验地址:http://pan.baidu.com/s/1YOVD8nkoOSYwX9KgSauLeQ 提取码:kcgz 文件名:bin_video_demo/bin_linux_video。

四、相关代码

#include "ffmpegsync.h"
#include "ffmpeghelper.h"
#include "ffmpegthread.h"

FFmpegSync::FFmpegSync(quint8 type, QObject *parent) : QThread(parent)
{
    this->stopped = false;
    this->type = type;
    this->thread = (FFmpegThread *)parent;
}

FFmpegSync::~FFmpegSync()
{

}

void FFmpegSync::run()
{
    if (!thread) {
        return;
    }

    this->reset();
    while (!stopped) {
        //暂停状态或者切换进度中或者队列中没有帧则不处理
        if (!thread->isPause && !thread->changePosition && packets.size() > 0) {
            mutex.lock();
            AVPacket *packet = packets.first();
            mutex.unlock();

            //h264的裸流文件同步有问题因为获取不到pts和dts(暂时用最蠢的延时办法解决)
            if (thread->formatName == "h264") {
                int sleepTime = (1000 / thread->frameRate) - 5;
                msleep(sleepTime);
            }

            //计算当前帧显示时间(外部时钟同步)
            ptsTime = FFmpegHelper::getPtsTime(thread->formatCtx, packet);
            if (!this->checkPtsTime()) {
                msleep(1);
                continue;
            }

            //显示当前的播放进度
            this->checkShowTime();

            //如果解码线程停止了则不用处理
            if (!thread->stopped) {
                //0-表示音频 1-表示视频
                if (type == 0) {
                    thread->decodeAudio1(packet);
                } else if (type == 1) {
                    thread->decodeVideo1(packet);
                }
            }

            //释放资源并移除
            mutex.lock();
            FFmpegHelper::freePacket(packet);
            packets.removeFirst();
            mutex.unlock();
        }

        msleep(1);
    }

    this->reset();
    this->clear();
    stopped = false;
}

void FFmpegSync::stop()
{
    if (this->isRunning()) {
        stopped = true;
        this->wait();
    }
}

void FFmpegSync::clear()
{
    mutex.lock();
    //释放还没有来得及处理的剩余的帧
    foreach (AVPacket *packet, packets) {
        FFmpegHelper::freePacket(packet);
    }
    packets.clear();
    mutex.unlock();
}

void FFmpegSync::reset()
{
    //复位音频外部时钟
    showTime = 0;
    bufferTime = 0;
    offsetTime = -1;
    startTime = av_gettime();
}

void FFmpegSync::append(AVPacket *packet)
{
    mutex.lock();
    packets << packet;
    mutex.unlock();
}

int FFmpegSync::getPacketCount()
{
    return this->packets.size();
}

bool FFmpegSync::checkPtsTime()
{
    //下面这几个时间值是参考的别人的
    bool ok = false;
    if (ptsTime > 0) {
        if (ptsTime > offsetTime + 100000) {
            bufferTime = ptsTime - offsetTime + 100000;
        }

        int offset = (type == 0 ? 1000 : 5000);
        //做梦都想不到倍速播放就这里控制个系数就行
        offsetTime = (av_gettime() - startTime) * thread->speed + bufferTime;
        if ((offsetTime <= ptsTime && ptsTime - offsetTime <= offset) || (offsetTime > ptsTime)) {
            ok = true;
        }
    } else {
        ok = true;
    }

    return ok;
}

void FFmpegSync::checkShowTime()
{
    //必须是文件(本地文件或网络文件)才有播放进度
    if (!thread->getIsFile()) {
        return;
    }

    //过滤重复发送播放时间
    bool showPosition = false;
    bool existVideo = (thread->videoIndex >= 0);
    if (type == 0) {
        //音频同步线程不能存在视频
        if (!existVideo) {
            showPosition = true;
        }
    } else if (type == 1) {
        //视频同步线程必须存在视频
        if (existVideo) {
            showPosition = true;
        }
    }

    //需要显示时间的时候发送对应进度(限定差值大于200毫秒没必要频繁发送)
    if (showPosition && (offsetTime - showTime > 200000)) {
        showTime = offsetTime;
        thread->position = showTime / 1000;
        emit receivePosition(thread->position);
    }
}

五、功能特点

5.1 基础功能

5.2 特色功能

5.3 视频控件

5.4 内核ffmpeg

加载全部内容

相关教程
猜你喜欢
用户评论