ffmpeg学习日记122-视频-获取视频的解码器,yuv格式名称,理解编码格式,封装格式,yuv格式的关系

Author: wencoo
Blog:https://wencoo.blog.csdn.net/
Date: 25/05/2023
Email: [email protected]
Wechat:wencoo824
QQ:1419440391
Details:

在这里插入图片描述

正文 或 背景

为什么要获取视频或者图片的解码器,以及yuv格式,因为,在以下场景中,我有一段png图片,将其解码之后,重新封装为另一种mp4视频,这里我不知道png使用的解码器是哪个,其解码出来的yuv数据又是什么格式,所以可以将解码过程中这两个数据找出来,打印出来,方便后面对mp4重新封装。

获取像素格式,也就是yuv排列格式

代码如下:

AVFormatContext *pFormatCtx = NULL;
	if (avformat_open_input(&pFormatCtx, inFileName, NULL, NULL) != 0)
	{
		fprintf(stderr, "Couldn't open input filen");
		return -1;
	}

	if (avformat_find_stream_info(pFormatCtx, 0) < 0)
	{
		fprintf(stderr, "av_find_stream_info ERRORn");
		return -1;
	}

	int videoStream = -1;
	for (int i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoStream = i;
			fprintf(stderr, "the first video stream index: videoStream = %d\n", videoStream);
			break;
		}
	}
	if (videoStream == -1)
		return -1; // Didn't find a video stream

	AVCodecContext *codeCtx = pFormatCtx->streams[videoStream]->codec;
	if (!codeCtx)
	{
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	std::cout << "pix_fmt:" << codeCtx->pix_fmt << std::endl;

AVCodecContext结构中的pix_fmt字段表示该素材的像素格式。

获取解码器id

接着上面的代码:

//打开mp4文件
	const AVCodec *codec = avcodec_find_decoder(codeCtx->codec_id);
	if (!codec)
	{
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

	std::cout << "Codec id:" << avcodec_get_name(codeCtx->codec_id) << std::endl;

AVCodecContext结构中的codec_id字段表示该素材的解码器id,通过改id可以找到解码器上下文,可以了输出名字。

获取输出文件的封装格式

封装格式,在调用代码avformat_alloc_output_context2时已经根据传入的文件名后缀解析好了,并把响应数据填充到ptOutFormatContext结构中,不需要我们显示的进行指定了。

AVFormatContext *ptOutFormatContext = NULL; //输出文件的封装格式上下文,内部包含所有的视频信息
	AVPacket tOutPacket = {0};					//存储一帧压缩编码数据给输出文件

	avformat_alloc_output_context2(&ptOutFormatContext, NULL, NULL, outFileName);
	if (!ptOutFormatContext)
	{
		printf("Could not create output context\r\n");
		ret = AVERROR_UNKNOWN;
	}
	if (avio_open(&ptOutFormatContext->pb, outFileName, AVIO_FLAG_WRITE))
	{
		return ret;
	}

	std::cout << "ptOutFormatContext ->oformat->name:" << ptOutFormatContext->oformat->name << std::endl;
	std::cout << "ptOutFormatContext ->oformat->long_name:" << ptOutFormatContext->oformat->long_name << std::endl;
	std::cout << "ptOutFormatContext ->oformat->video_codec:" << ptOutFormatContext->oformat->video_codec << std::endl;

参考:FFmpeg从入门:FFmpeg框架中的AVOutputFormat 的数据结构

在设置输出时,需要手动设置编码参数

// 2.分配内存
	AVCodecContext *codec_out_ctx = avcodec_alloc_context3(codec_out);
	if (!codec_out_ctx)
	{
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	// 3.设置编码参数
	/* 设置分辨率*/
	codec_out_ctx->width = 48;
	codec_out_ctx->height = 48;
	/* 设置time base */
	codec_out_ctx->time_base = (AVRational){1, 25};
	codec_out_ctx->framerate = (AVRational){25, 1};
	/* 设置I帧间隔
	 * 如果frame->pict_type设置为AV_PICTURE_TYPE_I, 则忽略gop_size的设置,一直当做I帧进行编码
	 */
	codec_out_ctx->gop_size = 25; // I帧间隔
	codec_out_ctx->max_b_frames = 2; // 如果不想包含B帧则设置为0
	codec_out_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	codec_out_ctx->codec_id = AV_CODEC_ID_H264;
	codec_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO;

其中,这两行需要手动填入的,这里一定要清楚其中的关系。

codec_out_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_out_ctx->codec_id = AV_CODEC_ID_H264;

理解编码格式,封装格式,yuv格式的关系

  1. 编码格式:比如h264,h265,mpeg4,vp8,vp9这些是编码格式

每一种编码格式,都有对应支持的的yuv格式,不同的编码格式支持的yuv格式有交集,也有不同,可以通过如下指令进行查询:

ffmpeg -h encoder=h264

参考解决:FFmpeg报错:Specified pixel format yuvj420p is invalid or not supported(用ffmpeg程序查看编码器支持像素格式命令)

  1. 封装格式:比如mp4,mkv,avi,等
    通过获取输出文件的封装格式一节可以看到,每一种封住格式其实也会关联一种编码格式,其实一种封装格式,可以支持多种编码格式,但不是支持所有编码格式!不是支持所有编码格式!不是支持所有编码格式!重要的事情说三遍,所以封装的时候,yuv直接生成mp4,需要查看编码格式与分装个是全部支持,现如今流行的封装格式如下:

|名称 |推出机构 |流媒体 |支持的视频编码 |支持的音频编码 |目前使用领域|
|:= |=: |=: |=: |=: |=: |
|AVI |Microsoft Inc. |不支持| 几乎所有格式 |几乎所有格式 |BT下载影视|
|MP4 |MPEG |支持 |MPEG-2,MPEG-4,H.264,H.263等 |AAC,MPEG-1 layers I, II, III, AC-3| 互联网视频网站
|TS |MPEG |支持 |MPEG-1,MPEG-2,MPEG-4,H.264 |MPEG-1 Layers I, II, III, AAC |IPTV,数字电视
|FLV| Adobe Inc.| 支持 |Sorenson, VP6, H.264 |MP3, ADPCM, Linear PCM, AAC等| 互联网视频网站
|MKV| CoreCodec Inc.| 支持| 几乎所有格式| 几乎所有格式 |互联网视频网站
|RMVB| Real Networks Inc. |支持| RealVideo 8, 9, 10 |AAC, Cook Codec, RealAudio Lossless |BT下载影视

参考:FFMPEG视音频编解码学习(一)
3. yuv格式:很多,enum AVPixelFormat pix_fmt;枚举里罗列的的都是yuv的格式,通过下面的命令

ffmpeg -h encoder=h264

可以看到,某一个编码器,支持的yuv格式是有限的,不同的yuv格式需要不同的编码器,也就是编码协议来支持。

总结

所以,我想要将png图片直接封装成mp4是不可以的,需要解码,解码之后得到的是png的yuv格式数据,需要将png格式的yuv数据(带透明通道)转换成yuv420的格式数据,才能够将编码封装成的mp4正确播放,否则生成的mp4播放内容是错误的。

代码

一下代码不包含png格式的yux数据转换成yuv420的转换功能,只是一个png转mp4的流程框架。

int pngToMp4()
{
	std::cout << "Hello World!" << std::endl;
	printf("ffmpeg version:%s\n", av_version_info());

	int ret = 0;

	// input yuv
	//打开png图片
	// png转换为yuv指令为:ffmpeg -i %4d.png -pix_fmt yuv420p -s 1984x1344 out.yuv
	//==========================输入配置========================================
	FILE *inFile = NULL;
	const char *inFileName = "/home/wencoo/gitcode/wen-coo-repo/study_ffmpeg/linuxPlatform/9-filterMoreChannel/filterMoreChannel/build/bg.png";
	inFile = fopen(inFileName, "rb+");
	if (!inFile)
	{
		printf("Fail to open file\n");
		return -1;
	}

	int in_width = 48;
	int in_height = 48;

	AVFormatContext *pFormatCtx = NULL;
	if (avformat_open_input(&pFormatCtx, inFileName, NULL, NULL) != 0)
	{
		fprintf(stderr, "Couldn't open input filen");
		return -1;
	}

	if (avformat_find_stream_info(pFormatCtx, 0) < 0)
	{
		fprintf(stderr, "av_find_stream_info ERRORn");
		return -1;
	}

	int videoStream = -1;
	for (int i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoStream = i;
			fprintf(stderr, "the first video stream index: videoStream = %d\n", videoStream);
			break;
		}
	}
	if (videoStream == -1)
		return -1; // Didn't find a video stream

	AVCodecContext *codeCtx = pFormatCtx->streams[videoStream]->codec;
	if (!codeCtx)
	{
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	std::cout << "codeCtx->pix_fmt:" << codeCtx->pix_fmt << std::endl;

	//打开mp4文件
	const AVCodec *codec = avcodec_find_decoder(codeCtx->codec_id);
	if (!codec)
	{
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

	std::cout << "codeCtx->codec_id :" << codeCtx->codec_id << std::endl;
	std::cout << "codeCtx->codec_id name:" << avcodec_get_name(codeCtx->codec_id) << std::endl;

	if (avcodec_open2(codeCtx, codec, NULL) < 0)
	{
		fprintf(stderr, "Could not open codec\n");
		exit(1);
	}

	AVFrame *frame = av_frame_alloc();
	if (!frame)
	{
		fprintf(stderr, "Could not allocate video frame\n");
		exit(1);
	}

	AVPacket *pkt = av_packet_alloc();
	if (!pkt)
	{
		fprintf(stderr, "Could not allocate video packet\n");
		exit(1);
	}

	uint8_t *video_dst_data[4] = {NULL};
	int video_dst_linesize[4] = {0};
	ret = av_image_alloc(video_dst_data, video_dst_linesize, in_width, in_height, codeCtx->pix_fmt, 1);
	if (ret < 0)
	{
	}
	int video_dst_bufsize = ret;

	//==========================输出配置===============================================
	// output yuv
	FILE *outFile = NULL;
	const char *codec_name = "libx264";
	const char *outFileName = "/home/wencoo/gitcode/wen-coo-repo/study_ffmpeg/linuxPlatform/9-filterMoreChannel/filterMoreChannel/build/bg.mp4";
	outFile = fopen(outFileName, "wb");
	if (!outFile)
	{
		printf("Fail to create file for output\n");
		return -1;
	}

	AVFormatContext *ptOutFormatContext = NULL; //输出文件的封装格式上下文,内部包含所有的视频信息
	AVPacket tOutPacket = {0};					//存储一帧压缩编码数据给输出文件

	avformat_alloc_output_context2(&ptOutFormatContext, NULL, NULL, outFileName);
	if (!ptOutFormatContext)
	{
		printf("Could not create output context\r\n");
		ret = AVERROR_UNKNOWN;
	}
	if (avio_open(&ptOutFormatContext->pb, outFileName, AVIO_FLAG_WRITE))
	{
		return ret;
	}

	std::cout << "ptOutFormatContext ->oformat->name:" << ptOutFormatContext->oformat->name << std::endl;
	std::cout << "ptOutFormatContext ->oformat->long_name:" << ptOutFormatContext->oformat->long_name << std::endl;
	std::cout << "ptOutFormatContext ->oformat->video_codec:" << ptOutFormatContext->oformat->video_codec << std::endl;
	std::cout << "ptOutFormatContext ->oformat->video_codec name:" << avcodec_get_name(ptOutFormatContext->oformat->video_codec) << std::endl;
	// 1.查找编码器
	const AVCodec *codec_out = avcodec_find_encoder_by_name(codec_name);
	if (!codec_out)
	{
		fprintf(stderr, "Codec '%s' not found\n", codec_name);
		exit(1);
	}

	// 2.分配内存
	AVCodecContext *codec_out_ctx = avcodec_alloc_context3(codec_out);
	if (!codec_out_ctx)
	{
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	// 3.设置编码参数
	/* 设置分辨率*/
	codec_out_ctx->width = 48;
	codec_out_ctx->height = 48;
	/* 设置time base */
	codec_out_ctx->time_base = (AVRational){1, 25};
	codec_out_ctx->framerate = (AVRational){25, 1};
	/* 设置I帧间隔
	 * 如果frame->pict_type设置为AV_PICTURE_TYPE_I, 则忽略gop_size的设置,一直当做I帧进行编码
	 */
	codec_out_ctx->gop_size = 25; // I帧间隔
	codec_out_ctx->max_b_frames = 2; // 如果不想包含B帧则设置为0
	codec_out_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	codec_out_ctx->codec_id = AV_CODEC_ID_H264;
	codec_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO;

	

	av_opt_set(codec_out_ctx->priv_data, "tune", "zerolatency", 0);

	if (codec_out->id == AV_CODEC_ID_H264)
	{
		// 相关的参数可以参考libx264.c的 AVOption options
		ret = av_opt_set(codec_out_ctx->priv_data, "preset", "medium", 0);
		if (ret != 0)
		{
			printf("av_opt_set preset failed\n");
		}
		ret = av_opt_set(codec_out_ctx->priv_data, "profile", "main", 0); // 默认是high
		if (ret != 0)
		{
			printf("av_opt_set profile failed\n");
		}
		ret = av_opt_set(codec_out_ctx->priv_data, "tune", "zerolatency", 0); // 直播是才使用该设置
		//        ret = av_opt_set(codec_ctx->priv_data, "tune","film",0); //  画质film
		if (ret != 0)
		{
			printf("av_opt_set tune failed\n");
		}
	}

	/* 设置bitrate */
	codec_out_ctx->bit_rate = 3000000;

	AVStream *avvideo_stream = avformat_new_stream(ptOutFormatContext, nullptr);
	avvideo_stream->codec = codec_out_ctx;
	avvideo_stream->time_base = codec_out_ctx->time_base;
	avvideo_stream->codec->codec_tag = 0;

	// 4.将codec_ctx和codec进行绑定
	ret = avcodec_open2(codec_out_ctx, codec_out, NULL);
	if (ret < 0)
	{
		//        fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
		exit(1);
	}
	printf("thread_count: %d, thread_type:%d\n", codec_out_ctx->thread_count, codec_out_ctx->thread_type);

	// // 5.打开输入和输出文件
	// FILE *infile = fopen(in_yuv_file, "rb");
	// if (!infile)
	// {
	// 	fprintf(stderr, "Could not open %s\n", in_yuv_file);
	// 	exit(1);
	// }

	// 6.分配pkt和frame
	AVPacket *pkt_out = av_packet_alloc();
	if (!pkt_out)
	{
		fprintf(stderr, "Could not allocate video frame\n");
		exit(1);
	}
	av_init_packet(pkt_out);
	AVFrame *frame_out = av_frame_alloc();
	if (!frame_out)
	{
		fprintf(stderr, "Could not allocate video frame\n");
		exit(1);
	}

	if (avformat_write_header(ptOutFormatContext, NULL) < 0) // avformat_write_header()中最关键的地方就是调用了AVOutputFormat的write_header()
	{														 //不同的AVOutputFormat有不同的write_header()的实现方法
		printf("Error occurred when opening output file\r\n");
	}

	// 7.为frame分配buffer
	frame_out->format = codec_out_ctx->pix_fmt;
	frame_out->width = codec_out_ctx->width;
	frame_out->height = codec_out_ctx->height;
	ret = av_frame_get_buffer(frame_out, 0);
	if (ret < 0)
	{
		fprintf(stderr, "Could not allocate the video frame data\n");
		exit(1);
	}
	// 计算出每一帧的数据 像素格式 * 宽 * 高
	// 1382400
	int frame_bytes = av_image_get_buffer_size((enum AVPixelFormat)frame_out->format, frame_out->width,
											   frame_out->height, 1);

	printf("frame_bytes %d\n", frame_bytes);
	uint8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);
	if (!yuv_buf)
	{
		printf("yuv_buf malloc failed\n");
		return 1;
	}
	int64_t begin_time = get_time();
	int64_t end_time = begin_time;
	int64_t all_begin_time = get_time();
	int64_t all_end_time = all_begin_time;
	int64_t pts = 0;

	//==========================编解码==================================================
	int indexNum = 1;
	while (av_read_frame(pFormatCtx, pkt) >= 0)
	{
		if (pkt->stream_index == videoStream)
		{
			ret = avcodec_send_packet(codeCtx, pkt);

			while (ret >= 0)
			{
				ret = avcodec_receive_frame(codeCtx, frame);
				if (ret < 0 || ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
				{
					break;
				}

				if (codeCtx->codec->type == AVMEDIA_TYPE_VIDEO)
				{
					//获取到了yuv视频帧
					av_image_copy(video_dst_data, video_dst_linesize,
								  (const uint8_t **)(frame->data), frame->linesize,
								  codeCtx->pix_fmt, in_width, in_height);

					ret = av_frame_make_writable(frame_out);
					int need_size = av_image_fill_arrays(frame_out->data, frame_out->linesize, video_dst_data[0],
														 (enum AVPixelFormat)frame_out->format,
														 frame_out->width, frame_out->height, 1);
					if (need_size != frame_bytes)
					{
						printf("av_image_fill_arrays failed, need_size:%d, frame_bytes:%d\n",
							   need_size, frame_bytes);
						break;
					}
					// indexNum++;
					pts += 1;
					// 设置pts
					frame_out->pts = pts; // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
					begin_time = get_time();

					ret = avcodec_send_frame(codec_out_ctx, frame_out);
					if (ret < 0)
					{
						std::cout << "failed avcodec_send_frame ret :" << ret << std::endl;
						ret = -1;
					}
					else
					{
						while (ret >= 0)
						{
							ret = avcodec_receive_packet(codec_out_ctx, pkt_out);
							std::cout << "1329 AVERROR(EAGAIN):" << AVERROR(EAGAIN) << " -- AVERROR_EOF:" << AVERROR_EOF << std::endl;
							if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
							{
								ret = 0;
								av_packet_unref(pkt_out);
								break;
							}
							else if (ret < 0)
							{
								ret = -1;
								av_packet_unref(pkt_out);
								break;
							}
							//                fwrite(pkt->data,1,pkt->size,outfile);

							//对264数据进行数据封装
							av_packet_rescale_ts(pkt_out, codec_out_ctx->time_base, avvideo_stream->time_base);
							pkt_out->stream_index = avvideo_stream->index;
							av_interleaved_write_frame(ptOutFormatContext, pkt_out);
						}
					}
				}
			}
		}
		av_packet_unref(pkt);
		if (ret < 0)
			break;
	}

	// 9.冲刷编码器
	ret = avcodec_send_frame(codec_out_ctx, nullptr);
	if (ret < 0)
	{
		ret = -1;
	}
	else
	{
		while (ret >= 0)
		{
			ret = avcodec_receive_packet(codec_out_ctx, pkt_out);
			std::cout << "1369 AVERROR(EAGAIN):" << AVERROR(EAGAIN) << " -- AVERROR_EOF:" << AVERROR_EOF << std::endl;
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			{
				ret = 0;
				av_packet_unref(pkt_out);
				break;
			}
			else if (ret < 0)
			{
				ret = -1;
				av_packet_unref(pkt_out);
				break;
			}
			//            fwrite(pkt->data,1,pkt->size,outfile);
			av_packet_rescale_ts(pkt_out, codec_out_ctx->time_base, avvideo_stream->time_base);
			pkt_out->stream_index = avvideo_stream->index;
			av_interleaved_write_frame(ptOutFormatContext, pkt_out);
		}
	}

	// mp4封装不对,这里写文件尾会导致段错误
	av_write_trailer(ptOutFormatContext);

	fclose(inFile);
	fclose(outFile);

	av_free(avvideo_stream);
	av_frame_free(&frame_out);
	av_packet_free(&pkt_out);
	avcodec_free_context(&codec_out_ctx);

	if (ptOutFormatContext)
	{
		avio_close(ptOutFormatContext->pb);
		// avformat_free_context(ptOutFormatContext); //释放空间
	}

	return 0;
}

报错:Specified pixel format rgba is invalid or not supported

报错如下:

 Specified pixel format rgba is invalid or not supported

参考解决:FFmpeg报错:Specified pixel format yuvj420p is invalid or not supported(用ffmpeg程序查看编码器支持像素格式命令)

补充:

参考

技术交流

欢迎加微信,搜索"wencoo824",进行技术交流,备注”博客音视频技术交流“

猜你喜欢

转载自blog.csdn.net/bootleader/article/details/130958561