Author: wencoo
Blog:https://wencoo.blog.csdn.net/
Date: 25/05/2023
Email: jianwen056@aliyun.com
Wechat:wencoo824
QQ:1419440391
Details:
文章目录
- 正文 或 背景
- 获取像素格式,也就是yuv排列格式
- 获取解码器id
- 获取输出文件的封装格式
- 在设置输出时,需要手动设置编码参数
- 理解编码格式,封装格式,yuv格式的关系
- 总结
- 代码
- 报错:Specified pixel format rgba is invalid or not supported
- 参考
- 技术交流
正文 或 背景
为什么要获取视频或者图片的解码器,以及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格式的关系
- 编码格式:比如h264,h265,mpeg4,vp8,vp9这些是编码格式
每一种编码格式,都有对应支持的的yuv格式,不同的编码格式支持的yuv格式有交集,也有不同,可以通过如下指令进行查询:
ffmpeg -h encoder=h264
参考解决:FFmpeg报错:Specified pixel format yuvj420p is invalid or not supported(用ffmpeg程序查看编码器支持像素格式命令)
- 封装格式:比如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",进行技术交流,备注”博客音视频技术交流“