前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ffmpeg 视频解码h264和yuv

ffmpeg 视频解码h264和yuv

作者头像
曾大稳
发布2018-09-11 10:41:54
4.3K0
发布2018-09-11 10:41:54
举报
文章被收录于专栏:曾大稳的博客

之前学习 ffmpegandroid 平台上,发现很不方便,所以打算在 vs 上重新搭建环境,然后重新学习,之后如果需要用到的话在移植到其他平台。环境搭建参考的是: https://blog.csdn.net/weixinhum/article/details/37699025

环境

Microsoft Visual C++ 2017 vs2017 ffmpeg 3.4.2

步骤主要是以下几大步骤:

  1. 初始化
代码语言:javascript
复制
av_register_all();
avformat_network_init();

  1. AVFormatContext获取和初始化
代码语言:javascript
复制
//AVFormatContext获取
avformat_alloc_context()
//打开文件,和AVFormatContext关联
avformat_open_input()
//获取文件流信息
avformat_find_stream_info()

  1. 获取解码器
代码语言:javascript
复制
//AVCodecContext获取
avcodec_alloc_context3()
//将AVCodecParameters转换为AVCodecContext
avcodec_parameters_to_context()
//获取解码器
avcodec_find_decoder()
//打开解码器
avcodec_open2()

  1. 解码准备
代码语言:javascript
复制
//获取解码数据包装 AVFrame
av_frame_alloc()
//根据宽高,解码类型(yuv420)获取缓存buffer大小
av_image_get_buffer_size()
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的AVFrame里面
av_image_fill_arrays()
//获取编码数据 包装 AVPacket
av_packet_alloc()
//获取SwsContext 图片转换(宽高这些)需要用到
sws_getContext()

  1. 读取数据源解码存储
代码语言:javascript
复制
//读取编码数据源到AVPacket
av_read_frame()
//发送数据源    
avcodec_send_packet()
//解码数据源  ,和avcodec_send_packet配合使用
avcodec_receive_frame()
//图像转换
sws_scale()
//写入文件
fwrite()

  1. 回收

具体代码和步骤如下代码:

代码语言:javascript
复制
#include <iostream>

extern "C" {
	#include <libavformat/avformat.h>   
	#include <libavcodec/avcodec.h>   
	#include <libavutil/imgutils.h>
	#include <libswscale/swscale.h>
}

//初始化
void init() {
	av_register_all();
	avformat_network_init();
}

void log(const char * msg, int d=-1123) {
	if (d == -1123) {
		printf_s("%s\n", msg);
	}
	else {
		printf_s("%s  %d \n", msg ,d);
	}

}

	
int video2YuvAndH264(const char * filePath,FILE * yuvFilePath, FILE * h264FilePath){
	AVFormatContext * pFmtCtx = NULL;
	AVCodecContext *pCodecCtx = NULL;
	AVFrame *pFrame = NULL;
	AVFrame *pFrameYUV = NULL;
	uint8_t *outBuffer = NULL;
	AVPacket *pPacket = NULL;
	SwsContext *pSwsCtx = NULL;

	//1. 初始化
	init();
	//2. AVFormatContext获取
	pFmtCtx =  avformat_alloc_context();
	//3. 打开文件
	if (avformat_open_input(&pFmtCtx,filePath,NULL,NULL)!=0) {
		log("Couldn't open input stream.\n");
		return -1;
	}
	//4. 获取文件信息
	if (avformat_find_stream_info(pFmtCtx,NULL)<0) {
		log("Couldn't find stream information.");
		return -1;
	}
	//5. 获取视频的index
	int i = 0,videoIndex =-1;
	for (; i < pFmtCtx->nb_streams; i++) {
		if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoIndex = i;
			break;
		}
	}

	if (videoIndex == -1) {
		log("Didn't find a video stream.");
		return -1;
	}
	//6. 获取解码器并打开
	pCodecCtx = avcodec_alloc_context3(NULL);
	if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
		log("Didn't parameters to contex.");
		return -1;
	}
	AVCodec *pCodec =  avcodec_find_decoder(pCodecCtx->codec_id);
	if (pCodec == NULL) {
		log("Codec not found.");
		return -1;
	}
	if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
		log("Could not open codec.");
		return -1;
	}
	//7. 解码开始准备工作
	pFrame = av_frame_alloc();
	pFrameYUV = av_frame_alloc();

	//根据需要解码的类型,获取需要的buffer,不要忘记free
	outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)*sizeof(uint8_t));
	//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);

	pPacket = av_packet_alloc();
	log("--------------- File Information ----------------");
	av_dump_format(pFmtCtx, 0, filePath, 0);
	log("-------------------------------------------------");
	//获取SwsContext
	pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);

	int count=0;
	//8. 读取数据
	while (av_read_frame(pFmtCtx, pPacket) == 0) {//读取一帧压缩数据
		if (pPacket->stream_index == videoIndex) {
			//写入H264数据到文件
			fwrite(pPacket->data, 1, pPacket->size, h264FilePath); //把H264数据写入h264FilePath文件
			//解码数据
			//avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pPacket);
			if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
				log("Decode end or Error.\n");
				break;
			}else {//处理解码数据并写入文件
				avcodec_receive_frame(pCodecCtx,pFrame);

				if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
					pFrameYUV->data, pFrameYUV->linesize) == 0) {
					continue;
				}

				count++;
				int y_size = pCodecCtx->width*pCodecCtx->height;
				fwrite(pFrameYUV->data[0], 1, y_size, yuvFilePath);    //Y 
				fwrite(pFrameYUV->data[1], 1, y_size / 4, yuvFilePath);  //U
				fwrite(pFrameYUV->data[2], 1, y_size / 4, yuvFilePath);  //V
				log("Succeed to decode frame!",count);
			}
		}
		av_packet_unref(pPacket);
	}
	//flush decoder
	/*当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。
	因此需要通过“flush_decoder”将这几帧数据输出。
	“flush_decoder”功能简而言之即直接调用avcodec_decode_video2()获得AVFrame,而不再向解码器传递AVPacket。*/
	while (1) {
		if (avcodec_send_packet(pCodecCtx, pPacket)!= 0) {
			log("Decode end or Error.\n");
			break;
		}
		else {
			avcodec_receive_frame(pCodecCtx, pFrame);

			sws_scale(pSwsCtx, (const unsigned char *const *)pFrame->data, pFrame->linesize, 0,
				pCodecCtx->height,
				pFrameYUV->data, pFrameYUV->linesize);

			int y_size = pCodecCtx->width * pCodecCtx->height;
			// yuv-> 4:1:1
			fwrite(pFrameYUV->data[0], 1, static_cast<size_t>(y_size), yuvFilePath);    //Y
			fwrite(pFrameYUV->data[1], 1, static_cast<size_t>(y_size / 4), yuvFilePath);  //U
			fwrite(pFrameYUV->data[2], 1, static_cast<size_t>(y_size / 4), yuvFilePath);  //V
			log("Flush Decoder: Succeed to decode frame!", count);
		}
	}

	if (pSwsCtx != NULL) {
		sws_freeContext(pSwsCtx);
	}
	if (outBuffer != NULL) {
		av_free(outBuffer);
	}
	if (pFrameYUV != NULL) {
		av_frame_free(&pFrameYUV);
	}
	if (pFrame != NULL) {
		av_frame_free(&pFrame);
	}
	if (pCodecCtx != NULL) {
		avcodec_close(pCodecCtx);
	}
	if (pFmtCtx != NULL) {
		avformat_close_input(&pFmtCtx);
	}

}


int main()
{
	FILE * yuvFile;
	FILE * h264File;
	fopen_s(& yuvFile,"F:/视频资源/gxsp.yuv", "wb+");
	fopen_s(& h264File,"F:/视频资源/gxsp.h264", "wb+");
	video2YuvAndH264("F:/视频资源/gxsp.mp4", yuvFile, h264File);

	fclose(yuvFile);
	fclose(h264File);

	getchar();

    return 0;
}

在Android上进行视频解码

参考链接: https://blog.csdn.net/king1425/article/details/71160339

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档