编解码流程图
_______ ______________
| | | |
| 输入 | 分路 | 编码的数据分组| 解码
| 文件 | --------->| | -----+
|_______| |______________| |
v
_________
| |
| 解码 |
| 帧率 |
|_________|
________ ______________ |
| | | | |
| 输出 | <-------- |编码的数据分组 | <----+
| 文件 | 复用器 | | 编码
|________| |______________|
ffmpeg库分析
libavcodec 编码/解码库
libavfilter 基于图的框架编辑库
libavformat I/O 和复用/解复用库
libavdevice 特殊设备复用/解复用库
libavutil 常用实用程序库
libswresample 音频重采样、格式转换和混合
libpostproc 后处理库
libswscale 颜色转换和缩放库
关于ffmpeg可以参考:https://ffmpeg.org/ffmpeg.html
源代码分析
1. 头文件
#include <iostream>
#include <QMutex>
extern "C"
{
//调用FFMpeg的头文件
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
class XFFmpeg
{
public:
// 优化单例实现
static XFFmpeg* m_instance;
static XFFmpeg* getInstance();
/*
打开文件返回视频文件的总时间
若时间小于0,同样未打开成功
*/
int Open(const char *path);
/*
关闭文件
*/
void Close();
/*
读取视频的每一帧,返回每帧后需要清理空间
*/
AVPacket Read();
/*
读取到每帧数据后需要对其进行解码,返回它的pts
*/
int Decode(const AVPacket *pkt);
/*
将解码后的YUV视频帧转化为RGB格式
*/
bool ToRGB(char *out,int outwidth,int outheight);
/*
音频的重采样
*/
int ToPCM(char *out);
std::string GetError(); //获取错误信息
virtual ~XFFmpeg();
int totalMs = 0; //总时长
int videoStream = 0; //视频流
int audioStream = 1; //音频流
int fps = 0; //每秒的视频帧数
bool Seek(float pos); //设置进度条的拖动位置后播放
bool isPlay = false; //播放暂停
int pts; //获得当前解码帧的时间
int GetPts(const AVPacket *pkt);//获得视频解码前视频帧的pts
int sampleRate = 48000; //样本率
int sampleSize = 16; //样本大小
int channel = 2; //通道数
protected:
char errorbuff[1024]; //打开时发生的错误信息
QMutex mutex; //互斥变量,多线程时避免同时间的读写
AVFormatContext *ic = NULL; //解封装上下文
AVFrame *yuv = NULL; //解码后的视频帧数据
AVFrame *pcm = NULL; //解码后的音频数据
SwsContext *cCtx = NULL; //视频转码上下文
SwrContext *aCtx = NULL; //音频重采样上下文
private:
XFFmpeg();
};
2. 注册ffmpeg
av_register_all:注册ffmpeg库。
XFFmpeg* XFFmpeg::m_instance = nullptr;
XFFmpeg* XFFmpeg::getInstance()
{
if (nullptr == m_instance )
{
std::mutex mtx; //函数结束时锁资源释放
m_instance = new(std::nothrow) Singleton();
if (nullptr == m_instance )
{
return nullptr;
}
}
return m_instance;
}
XFFmpeg::XFFmpeg()
{
errorbuff[0] = '\0';
//注册FFMpeg的库
av_register_all();
}
3. 视频打开及处理
avformat_open_input:打开解封装器
avcodec_find_decoder: 查找解码器
avcodec_open2:打开解码器
int XFFmpeg::Open(const char *path)
{
Close();//打开前先关闭清理
mutex.lock();
//打开解封装器
int iRet = avformat_open_input(&ic, path, 0, 0);
if (0 != iRet)
{
mutex.unlock();//解锁
av_strerror(iRet, errorbuff, sizeof(errorbuff));//错误信息
printf("open %s failed :%s\n", path, errorbuff);
return 0;
}
totalMs = ic->duration / (AV_TIME_BASE);//获取视频的总时间
//打印视频详细信息
av_dump_format(ic, 0, path, 0);
//解码器
for (int i = 0; i < ic->nb_streams; i++)
{
AVCodecContext *enc = ic->streams[i]->codec;//解码上下文
if (enc->codec_type == AVMEDIA_TYPE_VIDEO)//判断是否为视频
{
videoStream = i;
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("not open! %s",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;
}
4. 视频读取
av_read_frame:读取视频帧
AVPacket XFFmpeg::Read()
{
AVPacket pkt;
memset(&pkt, 0, sizeof(AVPacket));
mutex.lock();
if (!ic)
{
mutex.unlock();
return pkt;
}
int err = av_read_frame(ic, &pkt);//读取视频帧
if (err != 0)//读取失败
{
av_strerror(err, errorbuff, sizeof(errorbuff));
}
mutex.unlock();
return pkt;
}
5. 视频解码
av_frame_alloc:内存空间分配
avcodec_send_packet:发送数据帧
avcodec_receive_frame:接收数据帧
int XFFmpeg::Decode(const AVPacket *pkt)
{
mutex.lock();
if (!ic)//若未打开视频
{
mutex.unlock();
return nullptr;
}
//申请解码的对象空间
if (nullptr == yuv )
{
yuv = av_frame_alloc();
}
if (nullptr == pcm)
{
pcm = av_frame_alloc();
}
AVFrame *frame = yuv;//此时的frame是解码后的视频流
if (pkt->stream_index == audioStream)//若未音频
{
frame = pcm;//此时frame是解码后的音频流
}
//发送之前读取的pkt
int iRet = avcodec_send_packet(ic->streams[pkt->stream_index]->codec, pkt);
if (0 != iRet)
{
mutex.unlock();
return nullptr;
}
//解码pkt后存入yuv中
iRet = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec, frame);
if (0 != iRet)
{
mutex.unlock();
return nullptr;
}
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;
}
6. YUV转RGB
sws_getCachedContext:初始化SwsContext
sws_scale:开始转码
bool XFFmpeg::ToRGB(char *out, int outwidth, int outheight)
{
mutex.lock();
if (!ic||!yuv)//未打开视频文件或者未解码
{
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;
}
7. 音频重采样
swr_alloc_set_opts:设置音频参数
swr_init:音频参数初始化
swr_convert:音频转码
av_samples_get_buffer_size:获取采样大小
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
);
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;
}
8. 视频偏移
av_seek_frame:视频偏移
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 iRet = av_seek_frame(ic, videoStream, stamp,
AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
avcodec_flush_buffers(ic->streams[videoStream]->codec);//刷新缓冲,清理掉
mutex.unlock();
if (iRet > 0)
return true;
return false;
}
9. 视频关闭
avformat_close_input:关闭上下文空间
av_frame_free:释放空间
void XFFmpeg::Close()
{
mutex.lock();//需要上锁,以防多线程中你这里在close,另一个线程中在读取,
if (ic) avformat_close_input(&ic); //关闭ic上下文空间
if (yuv) av_frame_free(&yuv); //关闭时释放解码后的视频帧空间
if (pcm) av_frame_free(&pcm); //关闭时释放解码后的音频空间
if (cCtx)
{
sws_freeContext(cCtx); //释放转码器上下文空间
cCtx = NULL;
printf("shifang zhuangmaqi");
}
if (aCtx)
{
swr_free(&aCtx);//释放音频上下文空间
}
mutex.unlock();
}
|