您的位置:首页 > 编程语言 > Qt开发

第三章:ffmpeg和QT开发播放器之视频的解码转码

2018-03-10 23:48 465 查看
写在前面:本章对一个视频进行解码转码处理,内容对应视频的3-3~3-6。文章末尾对这几节课进行总结,然后附加源代码。

1、打开解码器

       之前使用avformat_open_input,只是打开文件流而已。
       一个视频打开之后,不止一个流,有视频流、音频流、字幕流。
       其中ic结构体里面的ic->nb_streams存放了是这个视频里面有几种流。
       AVCodecContext *enc = ic->streams[i]->codec;
       通过对ic结构体中的stream结构体中的codec进行判断,找到我们要的AVMEDIA_TYPE_VIDEO,也就是视频流
       然后使用avcodec_find_decoder(enc->codec_id)函数来判断系统有没有这个编码器
       接着再调用avcodec_open2函数将它打开这个解码器。

2、分配帧空间

        AVFrame *yuv = av_frame_alloc();
       定义一个帧空间AVFrame用来存放解码过的内容。ffmpeg中解码出来的都是yuv格式的。这个是将每一帧的色彩度和亮度存放起来的,而不是色彩信息。很多压缩格式都是用yuv这种方式来进行压缩的,这种算法压缩率更高,存储空间更小,存储一个像素点只要2个字节,而rgb需要3个字节。
       av_frame_alloc();这个函数不是分配整个解码的空间,而是AVFrame这个对象的空间,主要是因为我们并没有传递视频宽高比例给它,而且ffmpeg可以不需要知道指定大小,也能解密。
       av_frame_alloc()这个函数里面做了挺多初始化的,包括空间的申请。调用了一个动态库。在动态库申请的空间只能在动态库中释放,后面调用它的函数就能直接释放空间。

3、开始解码

       ffmpeg解码的调用用新老版本两种,先调用老版本avcodec_decode_video2函数,老版本中,对音视频的解码是分开解码的,新版的则是将它们合起来。
       对于avcodec_decode_video2函数来说,它根据每次解码的结果来申请所需要的空间。
       新版方式是使用avcodec_send_packet发送数据去解码,然后再用avcodec_receive_frame获取解码的结果,将编码后的帧存放到yuv帧空间中。re = avcodec_send_packet(videoCtx, &pkt);
if (re != 0)
{
av_packet_unref(&pkt);
continue;
}
re = avcodec_receive_frame(videoCtx, yuv);
if (re != 0)
{
av_packet_unref(&pkt);
continue;
}

4、进行转码

      由于yuv格式没办法被播放器、显示器所接收,所以要被转换成RGB格式,需要打开一个转码器,还需
c2ad
要调用#include <libavformat/avformat.h>头文件,并包含这个动态库#pragma comment(lib,"swscale.lib")
       转码器最好在一开始的时候打开。
       通过sws_getCachedContext函数获取解码器的内容,并打开编码器。
struct SwsContext *sws_getCachedContext(struct SwsContext *context,
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
 context:转码器的内容,传null,就新创建一个转码器,如果传的不是null,会将传入的转码器context,跟缓冲里面的转码器进行比较(context后面的参数srcH等),是否一致,如果一致,就传回传入的这个context,如果不一致,就重新再创建一个。
       srcW、srcH:视频源的长和宽度,这个我们从videoCtx->width,videoCtx->height中获取。
       srcFormat:源的像素点格式,同样从这里获取videoCtx->pix_fmt,应该是yuv420
       dstW、dstH:目标的长宽度。
       dstFormat:输出的格式。这个要根据前端来设置的,我们使用的是QT,所以使用的是AV_PIX_FMT_BGRA
       flags:是指定用什么算法。我们这里使用的是SWS_BICUBIC
       后面的参数,暂时用不到。

       打开转码器后,使用sws_scale函数进行转码。
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);       c:这个也是传入刚刚打开的那个转码器cCtx。

       srcSlice:原数据的data,也就是我们解码出来的yuv的data
       srcStride:对应前一个参数data的一行数据的大小。
       srcSliceY:位置,但是目前没用到。
       srcSliceH:原数据的高度。
       dst:目标数据的data,这个data要去new一个空间,空间大小则是我们输出一帧画面的大小。
       dstStride:指的是目标数据,一行像素点的大小,即width*4,这个4就是RGBA(各占1个字节,所以要用到4字节)。
       返回值则是转码后的高度。

5、总结

       首先一个视频的处理简单来说就是先将视频文件打开,avformat_open_input打开视频文件之后,提取出视频里面的信息,存放在AVFormatContext *ic里面。
       那么一个视频文件被打开了,肯定先要被解码,那么就可以利用刚刚提取到的ic里面的视频格式信息,并使用avcodec_find_decoder函数来查找是否支持该文件格式的视频解码,如果有这个解码器,那么就使用avcodec_open2这个函数将解码器打开,解码器的信息可以使用AVCodecContext *videoCtx创建的容器里面。
       那么打开解码器之后呢,就可以开始视频解码,在代码中,就直接用一个死循环,做以下几个操作
       1)使用av_read_frame函数读取一帧数据,数据内容存放在pkt里面。
       2)通过avcodec_send_packet和avcodec_receive_frame函数来将该帧数据解码。
       3)由于解码出来的内容是yuv格式的,那么我们还得将它转换成RGB格式的,所以使用sws_getCachedContext函数打开转码器,然后再用sws_scale函数进行转码。   
       ps:这里的sws_getCachedContext函数设计的很巧妙,第一次传入的context为NULL,所以会为我们创建一个转码器,后面再调用它的时候,由于context的参数和之前一致,所以sws_getCachedContext直接return context,这样就不会多次创建转码器了。
       这次的代码如下:
#include "xplay.h"
#include <QtWidgets/QApplication>
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swscale.lib")
extern "C"{
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}

static double r2d(AVRational r)
{
return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}

#define OUT_WIDTH	800
#define OUT_HEIGHT	600

int main(int argc, char *argv[])
{
int totalSec = 0;
int pts = 0;
char re = 0;
av_register_all();
char *path = "my.mp4";
AVFormatContext *ic = NULL;		//存放视频(流)的文件信息
AVPacket pkt;					//存放读取视频的数据包
AVCodecContext *videoCtx = NULL;//创建解码器的容器
SwsContext *cCtx = NULL;		//创建转码器的容器

int err;
int got_picture = 0;		//在视频解码老版本函数avcodec_decode_video2中,传入的参数,如果函数获取到视频会返回1
int videoStream = 0;		//记录视频流存放在streams
数组里的第几个。
int outwidth = OUT_WIDTH;			//输出的宽度
int outheight = OUT_HEIGHT;			//输出的高度

char *rgb = new char[outwidth*outheight * 4];	//图像的数据RGBA

re = avformat_open_input(&ic, path, 0, 0);

if (re != 0)
{
char buf[1024] = { 0 };

av_strerror(re, buf, sizeof(buf));

printf("open failed %s\n",path, buf);
getchar();
return -1;
}

totalSec = ic->duration / AV_TIME_BASE;		//计算出进度条时间
printf("file totalSec is %d\n", totalSec);

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;
AVCodec *codec = avcodec_find_decoder(enc->codec_id); //判断系统有没有这个解码器

if (!codec)
{
printf("video code not find!!\n");
return -1;
}
err = avcodec_open2(enc, codec,NULL);	//找到系统中这个解码器的话,就打开这个解码器。
if (err != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf));
printf("buf\n", path, buf);
return -2;
}
printf("open avcodec_open2 success!!!\n");
}
}

AVFrame *yuv = av_frame_alloc();		//分配的视频帧

for (;;)			//解码
{
re = av_read_frame(ic, &pkt);
if (re != 0) break;
if (pkt.stream_index != videoStream)
{
av_packet_unref(&pkt);	//如果不是视频格式的 就释放掉
continue;
}

pts = pkt.pts * r2d(ic->streams[pkt.stream_index]->time_base) *1000;

re = avcodec_send_packet(videoCtx, &pkt);
if (re != 0)
{
av_packet_unref(&pkt);
continue;
}
re = avcodec_receive_frame(videoCtx, yuv);
if (re != 0)
{
av_packet_unref(&pkt);
continue;
}
printf("[D]");

//打开转码器
cCtx = sws_getCachedContext(cCtx, videoCtx->width,
videoCtx->height,
videoCtx->pix_fmt,
outwidth, outheight,
AV_PIX_FMT_BGRA,
SWS_BICUBIC,
NULL, NULL, NULL);

if (!cCtx)
{
printf("sws_getCachedContext failed!!\n");
break;
}

uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };	//转码的目标数据
data[0] = (uint8_t *)rgb;
int linesize[AV_NUM_DATA_POINTERS] = { 0 };
linesize[0] = outwidth * 4;

int h = sws_scale(cCtx,yuv->data,yuv->linesize,0,videoCtx->height,
data,
linesize);

if (h > 0)
printf("(%d)", h);

//re = avcodec_decode_video2(videoCtx, yuv, &got_picture, &pkt);
//if (got_picture)
//{
//	printf("%d\n",re);
//}

printf("pts = %d\n", pts);
av_packet_unref(&pkt);	//用于清理空间,防止内存泄露。
}

if (cCtx)
{
sws_freeContext(cCtx);
}

avformat_close_input(&ic);

QApplication a(argc, argv);
Xplay w;
w.show();
return a.exec();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: