FFmpeg开发(五)——Qt视频播放器之封装FFmpeg类(参考了暴风影音、迅雷影音)
上一篇介绍了,使用Qt和FFmpeg写的播放器。页面大家可以点进去查看和下载。
FFmpeg开发(四)——Qt实现一个视频播放器(参考了暴风影音、迅雷影音)
实现的效果如下:
本篇分享一下,封装的FFmpeg类,因为使用的是Qt,所以就按照C++的方式进行了封装。
构造函数:
FFmpeg::FFmpeg()
{
initData();
}
初始化信息:
void FFmpeg::initData()
{
m_errorBuff[0] = '\0';
av_register_all(); //注册FFMpeg的库
m_pAVFormatCtx = NULL;//解封装上下文
m_pYuvFrame = NULL;//解码后的视频帧数据
m_pPcmFrame = NULL;//解码后的音频数据
m_pSwsCtx = NULL;//视频转码上下文
m_pSwrCtx = NULL;//音频重采样上下文
m_totalMs = 0;//总时长
m_videoStream = 0;//视频流
m_audioStream = 1;//音频流
m_fps = 0;//每秒的视频帧数
m_pts = 0;//获得当前解码帧的时间
m_sampleRate = 48000;//样本率
m_sampleSize = 16;//样本大小
m_channel = 2;///通道数
m_isPlay = false;
}
打开文件并解码:
int FFmpeg::openFileCtx(const char *path)
{
closeAndFree(); //先关闭并清理.
m_mutex.lock();//锁
int ret = avformat_open_input(&m_pAVFormatCtx, path, 0, 0);//打开解封装器
if (ret != 0)//打开错误时
{
m_mutex.unlock();//解锁
av_strerror(ret, m_errorBuff, sizeof(m_errorBuff));//错误信息
printf("open %s failed :%s\n", path, m_errorBuff);
return 0;
}
m_totalMs = m_pAVFormatCtx->duration / (AV_TIME_BASE);//获取视频的总时间
//打印视频详细信息
av_dump_format(m_pAVFormatCtx, 0, path, 0);
//解码器
for (int i = 0; i < m_pAVFormatCtx->nb_streams; i++)
{
AVCodecContext *avCodecCtx = m_pAVFormatCtx->streams[i]->codec;//解码上下文
if (avCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO)//判断是否为视频
{
m_videoStream = i;
m_fps = r2d(m_pAVFormatCtx->streams[i]->avg_frame_rate);//获得每秒的视频帧数
AVCodec *avCodec = avcodec_find_decoder(avCodecCtx->codec_id);//查找解码器
if (!avCodec)//未找到解码器
{
m_mutex.unlock();
printf("video code not find\n");
return 0;
}
int err = avcodec_open2(avCodecCtx, avCodec, NULL);//打开解码器
if (err != 0)//未打开解码器
{
m_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 (avCodecCtx->codec_type == AVMEDIA_TYPE_AUDIO)//若为音频流
{
m_audioStream = i;//音频流
AVCodec *avCodec = avcodec_find_decoder(avCodecCtx->codec_id);//查找解码器
if (avcodec_open2(avCodecCtx, avCodec, NULL) < 0)
{
m_mutex.unlock();
return 0;
}
m_sampleRate = avCodecCtx->sample_rate;//样本率
m_channel = avCodecCtx->channels;//通道数
switch (avCodecCtx->sample_fmt)//样本大小
{
case AV_SAMPLE_FMT_S16://signed 16 bits
m_sampleSize = 16;
break;
case AV_SAMPLE_FMT_S32://signed 32 bits
m_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", m_totalMs / 60, m_totalMs % 60);//以分秒计时
m_mutex.unlock();
return m_totalMs;
}
释放相关资源:
void FFmpeg::closeAndFree()
{
m_mutex.lock();//需要上锁,以防多线程中你这里在close,另一个线程中在读取,
if (m_pAVFormatCtx) avformat_close_input(&m_pAVFormatCtx);//关闭上下文空间
if (m_pYuvFrame) av_frame_free(&m_pYuvFrame);//关闭时释放解码后的视频帧空间
if (m_pPcmFrame) av_frame_free(&m_pPcmFrame);//关闭时释放解码后的音频空间
if (m_pSwsCtx)
{
sws_freeContext(m_pSwsCtx);//释放转码器上下文空间
m_pSwsCtx = NULL;
}
if (m_pSwrCtx)
{
swr_free(&m_pSwrCtx);//释放音频上下文空间
}
m_mutex.unlock();
}
读取视频帧:
//读取视频的每一帧
AVPacket FFmpeg::readAVPacket()
{
AVPacket pkt;
memset(&pkt, 0, sizeof(AVPacket));
m_mutex.lock();
if (!m_pAVFormatCtx)
{
m_mutex.unlock();
return pkt;
}
int err = av_read_frame(m_pAVFormatCtx, &pkt);//读取视频帧
if (err != 0)//读取失败
{
av_strerror(err, m_errorBuff, sizeof(m_errorBuff));
}
m_mutex.unlock();
return pkt;
}
解码:
//读取到每帧数据后需要对其进行解码,返回它的pts
int FFmpeg::decodeAVPacket(const AVPacket *pkt)
{
m_mutex.lock();
if (!m_pAVFormatCtx)//若未打开视频
{
m_mutex.unlock();
return -1;
}
if (m_pYuvFrame == NULL)//申请解码的对象空间
{
m_pYuvFrame = av_frame_alloc();
}
if (m_pPcmFrame == NULL)
{
m_pPcmFrame = av_frame_alloc();
}
AVFrame *frame = m_pYuvFrame;//此时的frame是解码后的视频流
if (pkt->stream_index == m_audioStream)//若为音频
{
frame = m_pPcmFrame;//此时frame是解码后的音频流
}
int ret = avcodec_send_packet(m_pAVFormatCtx->streams[pkt->stream_index]->codec, pkt);//发送之前读取的pkt
if (ret != 0)
{
m_mutex.unlock();
return -1;
}
ret = avcodec_receive_frame(m_pAVFormatCtx->streams[pkt->stream_index]->codec, frame);//解码pkt后存入yuv中
if (ret != 0)
{
m_mutex.unlock();
return -1;
}
//qDebug() << "pts=" << frame->pts;
m_mutex.unlock();
int p = frame->pts*r2d(m_pAVFormatCtx->streams[pkt->stream_index]->time_base);//当前解码的显示时间
if (pkt->stream_index == m_audioStream)//为音频流时设置pts
m_pts = p;
return p;
}
转成RGB:
bool FFmpeg::ToRGB(char *out, int outwidth, int outheight)
{
m_mutex.lock();
if (!m_pAVFormatCtx || !m_pYuvFrame)//未打开视频文件或者未解码
{
m_mutex.unlock();
return false;
}
AVCodecContext *videoCtx = m_pAVFormatCtx->streams[m_videoStream]->codec;
m_pSwsCtx = sws_getCachedContext(m_pSwsCtx, videoCtx->width,//初始化一个SwsContext
videoCtx->height,
videoCtx->pix_fmt, //输入像素格式
outwidth, outheight,
AV_PIX_FMT_BGRA,//输出像素格式
SWS_BICUBIC,//转码的算法
NULL, NULL, NULL);
if (!m_pSwsCtx)
{
m_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(m_pSwsCtx, m_pYuvFrame->data, //当前处理区域的每个通道数据指针
m_pYuvFrame->linesize,//每个通道行字节数
0, videoCtx->height,//原视频帧的高度
data,//输出的每个通道数据指针
linesize//每个通道行字节数
);//开始转码
if (h > 0)
{
printf("(%d)", h);
}
m_mutex.unlock();
return true;
}
转成PCM:
int FFmpeg::ToPCM(char *out)
{
m_mutex.lock();
if (!m_pAVFormatCtx || !m_pPcmFrame || !out)//文件未打开,解码器未打开,无数据
{
m_mutex.unlock();
return 0;
}
AVCodecContext *avCodecCtx_Audio = m_pAVFormatCtx->streams[m_audioStream]->codec;//音频解码器上下文
if(m_pSwrCtx == NULL)
{
m_pSwrCtx = swr_alloc();//初始化
swr_alloc_set_opts(m_pSwrCtx,avCodecCtx_Audio->channel_layout,AV_SAMPLE_FMT_S16,
avCodecCtx_Audio->sample_rate,
avCodecCtx_Audio->channels,
avCodecCtx_Audio->sample_fmt,
avCodecCtx_Audio->sample_rate, 0,0);
swr_init(m_pSwrCtx);
}
uint8_t *data[1];
data[0] = (uint8_t *)out;
int len = swr_convert(m_pSwrCtx, data, 10000, (const uint8_t **)m_pPcmFrame->data, m_pPcmFrame->nb_samples);
if(len <= 0)
{
m_mutex.unlock();
return 0;
}
int outsize = av_samples_get_buffer_size(NULL, avCodecCtx_Audio->channels,
m_pPcmFrame->nb_samples,AV_SAMPLE_FMT_S16,0);
m_mutex.unlock();
return outsize;
}
FFmpeg类的封装,大致就是上面这几步。
之前的文章链接,大家可以点击参考:
FFmpeg开发(四)——Qt实现一个视频播放器(参考了暴风影音、迅雷影音)
FFmpeg开发(五)——Qt视频播放器之封装FFmpeg类(参考了暴风影音、迅雷影音)
FFmpeg开发(六)——Qt视频播放器之封装音频类(参考了暴风影音、迅雷影音)
FFmpeg开发(七)——Qt视频播放器之播放列表类(参考了暴风影音、迅雷影音)
FFmpeg开发(八)——Qt视频播放器之多线程的使用(参考了暴风影音、迅雷影音)
FFmpeg开发(九)——Qt视频播放器之快进滑动条(参考了暴风影音、迅雷影音)
下一篇为大家介绍
FFmpeg开发(六)——Qt视频播放器之封装音频类(参考了暴风影音、迅雷影音)
本文原创作者:冯一川(ifeng12358@163),未经作者授权同意,请勿转载。
更多推荐
FFmpeg开发(五)——Qt视频播放器之封装FFmpeg类(参考了暴风影音、迅雷影音)
发布评论