基于qt和arm开发的视频播放功能:在arm上面使用ffmpeg播放avi文件,加了音频解码后卡顿明显,只播视频帧率可以保持在30左右

通过av_gettime函数检测到主要耗时操作为,读取音视频帧、解码、重采样PCM,但是只有部分帧耗时较长。有时候重采样一帧音频数据为PCM要80多毫秒,有时候读取一帧数据要七八十毫秒。

一下为我使用ffmpeg的代码:

  1. XFFmpeg.cpp (这是我的ffmpeg封装工具栏)
#include "XFFmpeg.h"

//调用FFMpeg的lib库


XFFmpeg::XFFmpeg()
{
    errorbuff[0] = '\0';//初始化
    av_register_all();//注册FFMpeg的库
}


XFFmpeg::~XFFmpeg()
{
}
static double r2d(AVRational r)
{
    return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}
int XFFmpeg::Open(const char *path)
{
    Close();//打开前先关闭清理
    mutex.lock();//锁
    int re = avformat_open_input(&ic, path, 0, 0);//打开解封装器
    if (re != 0)//打开错误时
    {
        mutex.unlock();//解锁
        av_strerror(re, errorbuff, sizeof(errorbuff));//错误信息
        printf("open %s failed :%s\n", path, errorbuff);
        return 0;
    }
    //检索流信息
    if (avformat_find_stream_info(ic,NULL) < 0) {
        qDebug("Couldn't find stream information.");
        return -1;
    }
    totalMs = ic->duration / (AV_TIME_BASE);//获取视频的总时间
//解码器
    for (int i = 0; i < ic->nb_streams; i++)
    {
        AVCodecContext *enc = ic->streams[i]->codec;//解码上下文

        if (enc->codec_type == AVMEDIA_TYPE_VIDEO)//判断是否为视频
        {
            videoStream = i;
            //videoCtx = enc;
            fps = r2d(ic->streams[i]->avg_frame_rate);//获得视频得fps
            AVCodec *codec = avcodec_find_decoder(enc->codec_id);//查找解码器
            if (!codec)//未找到解码器
            {
                mutex.unlock();
                printf("video code not find\n");
                return 0;
            }
            int err = avcodec_open2(enc, codec, NULL);//打开解码器
            if (err != 0)//未打开解码器
            {
                mutex.unlock();
                char buf[1024] = { 0 };
                av_strerror(err, buf, sizeof(buf));
                printf(buf);
                return 0;
            }
            printf("open codec success!\n");
        }else if (enc->codec_type == AVMEDIA_TYPE_AUDIO)//若未音频流
        {
            audioStream = i;//音频流
            AVCodec *codec = avcodec_find_decoder(enc->codec_id);//查找解码器
            if (avcodec_open2(enc, codec, NULL) < 0)
            {
                mutex.unlock();
                return 0;
            }
            this->sampleRate = enc->sample_rate;//样本率
            this->channel = enc->channels;//通道数
            switch (enc->sample_fmt)//样本大小
            {
            case AV_SAMPLE_FMT_S16://signed 16 bits
                this->sampleSize = 16;
                break;
            case  AV_SAMPLE_FMT_S32://signed 32 bits
                this->sampleSize = 32;
            default:
                break;
            }
            printf("audio sample rate:%d sample size:%d chanle:%d\n",this->sampleRate,this->sampleSize,this->channel);

        }
    }//至此为打开解码器过程

    printf("file totalSec is %d-%d\n", totalMs/ 60, totalMs % 60);//以分秒计时
    mutex.unlock();
    return totalMs;
}

void XFFmpeg::Close()
{
    mutex.lock();//需要上锁,以防多线程中你这里在close,另一个线程中在读取,
     if (yuv) av_frame_free(&yuv);//关闭时释放解码后的视频帧空间
     if (cCtx)
     {
         sws_freeContext(cCtx);//释放转码器上下文空间
         cCtx = NULL;
     }
    if (ic) avformat_close_input(&ic);//关闭ic上下文
    mutex.unlock();

}

std::string XFFmpeg::GetError()
{
    mutex.lock();
    std::string re = this->errorbuff;
    mutex.unlock();
    return re;
}
AVPacket XFFmpeg::Read()
{
    int readStart=av_gettime();
    AVPacket pkt;
    memset(&pkt,0,sizeof(AVPacket));
    qDebug()<<"Read 1 memset vartime =:"<<((av_gettime()-readStart)/1000);
    mutex.lock();
    if (!ic)
    {
        mutex.unlock();
        return pkt;
    }
    int err = av_read_frame(ic, &pkt);//读取视频帧
    if (err != 0)//读取失败
    {
        av_strerror(err,errorbuff,sizeof(errorbuff));
    }
    qDebug()<<"Read 2 readStart to end =:"<<((av_gettime()-readStart)/1000);
    mutex.unlock();
    return pkt;

}
int XFFmpeg::Decode(const AVPacket *pkt)
{
    mutex.lock();
    if (!ic)//若未打开视频
    {
        mutex.unlock();
        return NULL;

    }
    if (yuv == NULL)//申请解码的对象空间
    {
        yuv = av_frame_alloc();
    }
    if (pcm == NULL)
    {
        pcm = av_frame_alloc();
    }
    AVFrame *frame = yuv;//此时的frame是解码后的视频流
    if (pkt->stream_index == audioStream)//若为音频
    {
        frame = pcm;//此时frame是解码后的音频流
    }
    int re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec, pkt);//发送之前读取的pkt
    if (re != 0)
    {
        mutex.unlock();
        return NULL;
    }
    re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec, frame);//解码pkt后存入yuv中
    if (re != 0)
    {
        mutex.unlock();
        return NULL;
    }
    qDebug() << "pts=" << frame->pts;

    mutex.unlock();
    int p = frame->pts*r2d(ic->streams[pkt->stream_index]->time_base);//当前解码的显示时间
    if (pkt->stream_index == audioStream)//为音频流时设置pts
        this->pts = p;
    return p;
}
bool XFFmpeg::ToRGB(const AVFrame *yuv, char *out, int outwidth, int outheight)
{

    mutex.lock();
    if (!ic)//未打开视频文件
    {
        mutex.unlock();
        return false;
    }
    AVCodecContext *videoCtx = ic->streams[this->videoStream]->codec;
    cCtx = sws_getCachedContext(cCtx, videoCtx->width,//初始化一个SwsContext
        videoCtx->height,
        videoCtx->pix_fmt, //输入像素格式
        outwidth, outheight,
        AV_PIX_FMT_BGRA,//输出像素格式
        SWS_BICUBIC,//转码的算法
        NULL, NULL, NULL);

    if (!cCtx)
    {
        mutex.unlock();
        printf("sws_getCachedContext  failed!\n");
        return false;
    }
    uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };
    data[0] = (uint8_t *)out;//第一位输出RGB
    int linesize[AV_NUM_DATA_POINTERS] = { 0 };

    linesize[0] = outwidth * 4;//一行的宽度,32位4个字节
    int h = sws_scale(cCtx, yuv->data, //当前处理区域的每个通道数据指针
        yuv->linesize,//每个通道行字节数
        0, videoCtx->height,//原视频帧的高度
        data,//输出的每个通道数据指针
        linesize//每个通道行字节数

        );//开始转码

    if (h > 0)
    {
        printf("(%d)", h);
    }
    mutex.unlock();
    return true;

}
int XFFmpeg::ToPCM(char *out)
{
    mutex.lock();
    if (!ic || !pcm || !out)//文件未打开,解码器未打开,无数据
    {
        mutex.unlock();
        return 0;
    }
    AVCodecContext *ctx = ic->streams[audioStream]->codec;//音频解码器上下文
    if (aCtx == NULL)
    {
        aCtx = swr_alloc();//初始化
        swr_alloc_set_opts(aCtx,ctx->channel_layout,
            AV_SAMPLE_FMT_S16,
              ctx->sample_rate,
              ctx->channels,
              ctx->sample_fmt,
              ctx->sample_rate,
              0,0
              );
        swr_init(aCtx);
    }
    uint8_t  *data[1];
    data[0] = (uint8_t *)out;

    //音频的重采样过程
    int len = swr_convert(aCtx, data, 10000,
        (const uint8_t **)pcm->data,
        pcm->nb_samples
        );
    qDebug()<<"len="<<len;
    if (len <= 0)
    {
        mutex.unlock();
        return 0;
    }
    int outsize = av_samples_get_buffer_size(NULL, ctx->channels,
        pcm->nb_samples,
        AV_SAMPLE_FMT_S16,
        0);

    mutex.unlock();
    return outsize;
}
bool XFFmpeg::Seek(float pos)
{
    mutex.lock();
    if (!ic)//未打开视频
    {
        mutex.unlock();
        return false;
    }
    int64_t stamp = 0;
    stamp = pos * ic->streams[videoStream]->duration;//当前它实际的位置
    pts = stamp * r2d(ic->streams[videoStream]->time_base);//获得滑动条滑动后的时间戳
    int re = av_seek_frame(ic, videoStream, stamp,
        AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);//将视频移至到当前点击滑动条位置
    avcodec_flush_buffers(ic->streams[videoStream]->codec);//刷新缓冲,清理掉
     mutex.unlock();
    if (re > 0)
        return true;
    return false;
}

XFFmpeg.h ffmpeg工具栏头文件


```c++

#ifndef XFFMPEG_H
#define XFFMPEG_H


#pragma once
#include <iostream>
#include <QMutex>
#include <QDebug>
extern "C"
{
    //调用FFMpeg的头文件
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/time.h>
#include <libswresample/swresample.h>
}

class XFFmpeg
{
public:
    static XFFmpeg *Get()//单件模式
    {
        static XFFmpeg ff;
        return &ff;
    }

///打开视频文件,如果上次已经打开会先关闭
///@para path  视频文件路径
///@return bool 成功失败,失败错误信息通过 GetError获取
    int Open(const char *path);//打开视频文件
    void Close();//关闭文件
    std::string GetError();//获取错误信息
    virtual ~XFFmpeg();
    int totalMs=0;//总时长
    //读取视频的每一帧,返回每帧后需要清理空间
    AVPacket Read();
    //读取到每帧数据后需要对其进行解码
    int Decode(const AVPacket *pkt);

    AVFrame *yuv = NULL;//解码后的视频帧数据
    AVFrame *pcm = NULL;
    int pts;
    int videoStream = 0;//视频流
    double fps;
    bool isPlay = false;//播放暂停
   //将解码后的YUV视频帧转化为RGB格式
    bool ToRGB(const AVFrame *yuv,char *out,int outwidth,int outheight);

    SwsContext  *cCtx = NULL;//转码器上下文
    bool Seek(float pos);
    int audioStream;//音频流

    int sampleRate = 48000;//样本率
    int sampleSize = 16;//样本大小
    int channel = 2;///通道数
    int ToPCM(char *out);
    SwrContext *aCtx = NULL;//音频重采样上下文
protected:
    char errorbuff[1024];//打开时发生的错误信息
    XFFmpeg();
    QMutex mutex;//互斥变量,多线程时避免同时间的读写
    AVFormatContext *ic = NULL;//解封装上下文

};
#endif // XFFMPEG_H

2. 我在一下线程中调用ffmpeg工具类:-------------------------------------------------
**XVideoThread.cpp**


```c++
#include "XVideoThread.h"
#include "XFFmpeg.h"
#include "XAudioPlay.h"
#include "SAudioPlayer.h"
#include <QDebug>
bool isexit = false;//线程未退出
XVideoThread::XVideoThread()
{
}


XVideoThread::~XVideoThread()
{
}

void XVideoThread::run()
{
    char out[10000] = {0};
    int64_t runTime=0;
    while (!isexit)//线程未退出
    {
        if (!XFFmpeg::Get()->isPlay)//如果为暂停状态,不处理
        {
            msleep(10);
            continue;
        }
        if(!SAudioPlayer::Get()->isEnable)
            continue;
        runTime = av_gettime();
 //       int free = SAudioPlayer::Get()->getChnBusyBuf();//此时缓冲区的空间大小,控制播放时间随音频解码时间
//        if (free ==0)
//        {
//            msleep(1);
//            continue;
//        }
        AVPacket pkt = XFFmpeg::Get()->Read();
          qDebug()<<"XFFmpeg Read runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
        runTime = av_gettime();
        if (pkt.size <= 0)//未打开视频
        {
            msleep(10);
            continue;
        }
        if (pkt.stream_index == XFFmpeg::Get()->audioStream)
        {
              qDebug()<<"audio start time : "<<av_gettime();
            XFFmpeg::Get()->Decode(&pkt);//解码音频
              qDebug()<<"audio Decode runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
            runTime = av_gettime();
            av_packet_unref(&pkt);//释放pkt包
              qDebug()<<"audio Decode av_packet_unref runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
            runTime = av_gettime();
            int len = XFFmpeg::Get()->ToPCM(out);//重采样音频
              qDebug()<<"audio ToPCM runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
            runTime = av_gettime();
              qDebug()<<"out="<<out;
              qDebug()<<"len="<<len;
            if(len<=0)
                continue;
          // SAudioPlayer::Get()->sendFrame((unsigned char*)out,len);//这个sendFrame函数是arm版中播放pcm音频数据的操作
              qDebug()<<"audio sendFrame runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
            continue;
        }
        XFFmpeg::Get()->Decode(&pkt);//解码视频帧
          qDebug()<<"vedio Decode runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
        runTime = av_gettime();
        av_packet_unref(&pkt);
          qDebug()<<"vedio Decode av_packet_unref runTime=: "<<(av_gettime()-runTime)/1000<<"av_gettime()  "<<av_gettime();
//        runTime = 1000/XFFmpeg::Get()->fps-(av_gettime()-runTime)/1000;
//        if(runTime>0)
//            msleep(runTime);
    }
}


XVideoThread.h 线程类的头文件

#ifndef XVIDEOTHREAD_H
#define XVIDEOTHREAD_H

#include <QThread>



class XVideoThread : public QThread
{
public:
    static XVideoThread *Get()//创建单例模式
    {
        static XVideoThread vt;
        return &vt;
    }
    void run();//线程的运行
    XVideoThread();
    virtual ~XVideoThread();
};

#endif // XVIDEOTHREAD_H