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