第三章:ffmpeg和QT开发播放器之视频的解码转码
2018-03-10 23:48
465 查看
写在前面:本章对一个视频进行解码转码处理,内容对应视频的3-3~3-6。文章末尾对这几节课进行总结,然后附加源代码。
一个视频打开之后,不止一个流,有视频流、音频流、字幕流。
其中ic结构体里面的ic->nb_streams存放了是这个视频里面有几种流。
AVCodecContext *enc = ic->streams[i]->codec;
通过对ic结构体中的stream结构体中的codec进行判断,找到我们要的AVMEDIA_TYPE_VIDEO,也就是视频流
然后使用avcodec_find_decoder(enc->codec_id)函数来判断系统有没有这个编码器
接着再调用avcodec_open2函数将它打开这个解码器。
定义一个帧空间AVFrame用来存放解码过的内容。ffmpeg中解码出来的都是yuv格式的。这个是将每一帧的色彩度和亮度存放起来的,而不是色彩信息。很多压缩格式都是用yuv这种方式来进行压缩的,这种算法压缩率更高,存储空间更小,存储一个像素点只要2个字节,而rgb需要3个字节。
av_frame_alloc();这个函数不是分配整个解码的空间,而是AVFrame这个对象的空间,主要是因为我们并没有传递视频宽高比例给它,而且ffmpeg可以不需要知道指定大小,也能解密。
av_frame_alloc()这个函数里面做了挺多初始化的,包括空间的申请。调用了一个动态库。在动态库申请的空间只能在动态库中释放,后面调用它的函数就能直接释放空间。
对于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;
}
c2ad
要调用#include <libavformat/avformat.h>头文件,并包含这个动态库#pragma comment(lib,"swscale.lib")
转码器最好在一开始的时候打开。
通过sws_getCachedContext函数获取解码器的内容,并打开编码器。
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字节)。
返回值则是转码后的高度。
那么一个视频文件被打开了,肯定先要被解码,那么就可以利用刚刚提取到的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,这样就不会多次创建转码器了。
这次的代码如下:
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(); }
相关文章推荐
- Android 本地视频播放器开发 —— ffmpeg解码视频文件中的音频
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)
- 使用 FFmpeg 开发播放器基础--使用 ffmpeg 解码视频文件
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Qt + ffmpeg 视频解码
- Android本地视频播放器开发--视频解码
- 移动视频监控(2)---原型开发---(音视频编解码多平台移植(for window/wince))ffmpeg --自由之路即是曲折之路。
- Qt+ffmpeg解码视频
- ffmpeg解码RTSP/TCP视频流H.264(QT界面显示视频画面)
- 第一章:ffmpeg和QT开发播放器之环境搭建
- ffmpeg开发:视频数据在qt显示
- FFMPEG+SDL2.0流媒体开发3---简易MP4视频播放器,提取MP4的H264视频序列解码并且显示
- QT开发用ffmpeg将图片制作成视频
- QT 使用phonon开发视频播放器遇到的问题(希望能帮到你)
- 音视频开发——ffmpeg解码(四)
- Android本地视频播放器开发--视频解码