ffmpeg的导入和视频解码,YUV保存(ffmpeg4.2)
1.分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行
2.接着调用打开视频文件
AVFormatContext * pFormatContext = avformat_alloc_context(); int ret = avformat_open_input(&pFormatContext,filepath,NULL,NULL);
3.文件打开成功后就是查找文件中的视频流了:
// 第三步:查找视频流 // 如果是视频解码,那么查找视频流,如果是音频解码,那么就查找音频流 // avformat_find_stream_info(); // AVProgram 视频的相关信息 ret = avformat_find_stream_info(pFormatContext, NULL); if(ret < 0) { cout << "find video stream failed "<<endl; } /* * 1.查找视频流索引位置 * */ int streamIndex =0,i; for(i=0; i< streamIndex; i++) { // 判断流的类型 // 旧的接口 formatContext->streams[i]->codec->codec_type // 4.0.0以后新加入的类型用于替代codec // codec -> codecpar enum AVMediaType mediaType = pFormatContext->streams[i]->codecpar->codec_type; if(mediaType == AVMEDIA_TYPE_VIDEO) //视频流 { streamIndex =i; break; } else if(mediaType == AVMEDIA_TYPE_AUDIO) { //音频流 } else { //其他流 } }
4.查找视频解码器
/* * 2.根据视频流索引,获取解码器上下文 * 旧的接口,拿到上下文,pFormatContext->streams[i]->codec * 4.0.0以后新加入的类型代替codec * codec-codecpar 此处的新接口不需要上下文 */ AVCodecParameters * avcodecParameters = pFormatContext->streams[streamIndex]->codecpar; enum AVCodecID codecId = avcodecParameters->codec_id; /* * 3.根据解码器上下文,获得解码器ID,然后查找解码器 * avvodec_find_encoder(enum AVCodecId id) 编码器 */ AVCodec * codec = avcodec_find_decoder(codecId);
5.打开解码器
/* * 第五步:打开解码器 * avcodec_open2() * 旧接口直接使用codec作为上下文传入 * pFormatContext->streams[avformat_stream_index]->codec被遗弃 * 新接口如下 */ AVCodecContext * avCodecContext = avcodec_alloc_context3(NULL); if(avCodecContext == NULL) { //创建解码器上下文失败 cout<<"create condectext failed "<<endl; return; } // avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par) // 将新的API中的 codecpar 转成 AVCodecContext avcodec_parameters_to_context(avCodecContext, avcodecParameters); ret = avcodec_open2(avCodecContext,codec,NULL); if(ret < 0) { cout << "open decoder failed "<< endl; return; } cout << "decodec name: "<< codec->name<< endl;
6.现在开始读取视频了
/* * 第六步:读取视频压缩数据->循环读取 * av_read_frame(AVFoematContext *s, AVPacket *packet) * s: 封装格式上下文 * packet:一帧的压缩数据 */ AVPacket *avPacket = (AVPacket *)av_mallocz(sizeof(AVPacket)); AVFrame *avFrameIn = av_frame_alloc(); //用于存放解码之后的像素数据 // sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param) // 原始数据 // scrW: 原始格式宽度 // scrH: 原始格式高度 // scrFormat: 原始数据格式 // 目标数据 // dstW: 目标格式宽度 // dstH: 目标格式高度 // dstFormat: 目标数据格式 // 当遇到Assertion desc failed at src/libswscale/swscale_internal.h:668 // 这个问题就是获取元数据的高度有问题 struct SwsContext *swsContext = sws_getContext(avcodecParameters->width, avcodecParameters->height, avCodecContext->pix_fmt, avcodecParameters->width, avcodecParameters->height, AV_PIX_FMT_YUV420P, SWS_BITEXACT, NULL, NULL, NULL); //创建缓冲区 //创建一个YUV420视频像素数据格式缓冲区(一帧数据) AVFrame *pAVFrameYUV420P = av_frame_alloc(); //给缓冲区设置类型->yuv420类型 //得到YUV420P缓冲区大小 // av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align) //pix_fmt: 视频像素数据格式类型->YUV420P格式 //width: 一帧视频像素数据宽 = 视频宽 //height: 一帧视频像素数据高 = 视频高 //align: 字节对齐方式->默认是1 int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, avCodecContext->width, avCodecContext->height, 1); cout << "size:"<<bufferSize<<endl; //开辟一块内存空间 uint8_t *outBuffer = (uint8_t *)av_malloc(bufferSize); //向pAVFrameYUV420P->填充数据 // av_image_fill_arrays(uint8_t **dst_data, int *dst_linesize, const uint8_t *src, enum AVPixelFormat pix_fmt, int width, int height, int align) //dst_data: 目标->填充数据(pAVFrameYUV420P) //dst_linesize: 目标->每一行大小 //src: 原始数据 //pix_fmt: 目标->格式类型 //width: 宽 //height: 高 //align: 字节对齐方式 av_image_fill_arrays(pAVFrameYUV420P->data, pAVFrameYUV420P->linesize, outBuffer, AV_PIX_FMT_YUV420P, avCodecContext->width, avCodecContext->height, 1); FILE * fileYUV420P = fopen("out.yuv","wb+"); int currentIndex=0,ySize,uSize,vSize; while(av_read_frame(pFormatContext,avPacket) >=0) { //判断是不是视频 if(avPacket->stream_index == streamIndex) { //读取每一帧数据,立马解码一帧数据 //解码之后得到视频的像素数据->YUV // avcodec_send_packet(AVCodecContext *avctx, AVPacket *pkt) // avctx: 解码器上下文 // pkt: 获取到数据包 // 获取一帧数据 avcodec_send_packet(avCodecContext,avPacket); //解码 ret = avcodec_receive_frame(avCodecContext,avFrameIn); if(ret == 0) //解码成功 { //此处无法保证视频的像素格式一定是YUV格式 //将解码出来的这一帧数据,统一转类型为YUV // sws_scale(struct SwsContext *c, const uint8_t *const *srcSlice, const int *srcStride, int srcSliceY, int srcSliceH, uint8_t *const *dst, const int *dstStride) // SwsContext *c: 视频像素格式的上下文 // srcSlice: 原始视频输入数据 // srcStride: 原数据每一行的大小 // srcSliceY: 输入画面的开始位置,一般从0开始 // srcSliceH: 原始数据的长度 // dst: 输出的视频格式 // dstStride: 输出的画面大小 sws_scale(swsContext,(const uint8_t * const *)avFrameIn->data, avFrameIn->linesize, 0, avCodecContext->height, pAVFrameYUV420P->data, pAVFrameYUV420P->linesize); //方式一:直接显示视频上面去 //方式二:写入yuv文件格式 //5、将yuv420p数据写入.yuv文件中 //5.1 计算YUV大小 //分析一下原理? //Y表示:亮度 //UV表示:色度 //有规律 //YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y) //YUV420P格式规范二:4个像素点对应一个(U和V: 4Y = U = V) ySize = avCodecContext->width * avCodecContext->height; uSize = ySize/4; vSize = ySize/4; fwrite(pAVFrameYUV420P->data[0],1,ySize,fileYUV420P); fwrite(pAVFrameYUV420P->data[1],1,uSize,fileYUV420P); fwrite(pAVFrameYUV420P->data[2],1,vSize,fileYUV420P); currentIndex++; cout <<"当前解码 "<<currentIndex<<"帧"<<endl; } } }
7.关闭解码器
/* * 关闭解码器 * */ av_packet_free(&avPacket); fclose(fileYUV420P); av_frame_free(&avFrameIn); av_frame_free(&pAVFrameYUV420P); free(outBuffer); avcodec_close(avCodecContext); avformat_free_context(pFormatContext);
代码地址:https://github.com/hgstudy/FFmpegStudy