Windows下ffmpeg调用测试(YUV420P编码)
1下载静态库和动态库
如果你是动态加载,可以直接使用动态库(dlopen等);否则就才用动态+ 静态的方式(动态库的静态加载方式);或者直接将静态库编译到你的执行程序(纯静态组件使用);
https://www.gyan.dev/ffmpeg/builds/#libraries
添加组件
vs2022: 属性->c/c++->附加包含目录;将头文件目录添加进去
属性->链接器->附加库目录,添加静态库路径;然后运行的时候记得吧dll放到EXE目录
2 .YUV420P 编码264
选择YUV420P(YV12)是因为,这个数据我可以通过FFmpeg命令行解析文件得到,用来测试,而且planner格式的数据Y,V,U是连续且单独放置的,不会存在UV交叉;YUV一帧的数据量就是width*heigt*1.5
随便找一个MP4录像,用FFmpeg命令行得到YUV数据,用来测试,ffmpeg -i input.mp4 -vf "format=yuv420p" output.yuv;如果你本身就有YUV测试数据,或者数据来源于实时的录像,截屏等等,就可以直接用;
下边的是FFmpeg提供的测试demo的源码,是自己生成的YUV,然后设置编码参数,输出264;我后边会根据这个demo进行一点点改动;不过最主要还是使用这个demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | /* * Copyright (c) 2001 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * @file libavcodec encoding video API usage example * @example encode_video.c * * Generate synthetic video data and encode it to an output file. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt, FILE *outfile) { int ret; /* send the frame to the encoder */ if (frame) printf( "Send frame %3" PRId64 "\n" , frame->pts); ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { fprintf(stderr, "Error sending a frame for encoding\n" ); exit(1); } while (ret >= 0) { ret = avcodec_receive_packet(enc_ctx, pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return ; else if (ret < 0) { fprintf(stderr, "Error during encoding\n" ); exit(1); } printf( "Write packet %3" PRId64 " (size=%5d)\n" , pkt->pts, pkt->size); fwrite(pkt->data, 1, pkt->size, outfile); av_packet_unref(pkt); } } int main( int argc, char **argv) { const char *filename, *codec_name; const AVCodec *codec; AVCodecContext *c= NULL; int i, ret, x, y; FILE *f; AVFrame *frame; AVPacket *pkt; uint8_t endcode[] = { 0, 0, 1, 0xb7 }; if (argc <= 2) { fprintf(stderr, "Usage: %s <output file> <codec name>\n" , argv[0]); exit(0); } filename = argv[1]; codec_name = argv[2]; /* find the mpeg1video encoder */ codec = avcodec_find_encoder_by_name(codec_name); if (!codec) { fprintf(stderr, "Codec '%s' not found\n" , codec_name); exit(1); } c = avcodec_alloc_context3(codec); if (!c) { fprintf(stderr, "Could not allocate video codec context\n" ); exit(1); } pkt = av_packet_alloc(); if (!pkt) exit(1); /* put sample parameters */ c->bit_rate = 400000; /* resolution must be a multiple of two */ c->width = 352; c->height = 288; /* frames per second */ c->time_base = (AVRational){1, 25}; c->framerate = (AVRational){25, 1}; /* emit one intra frame every ten frames * check frame pict_type before passing frame * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I * then gop_size is ignored and the output of encoder * will always be I frame irrespective to gop_size */ c->gop_size = 10; c->max_b_frames = 1; c->pix_fmt = AV_PIX_FMT_YUV420P; if (codec->id == AV_CODEC_ID_H264) av_opt_set(c->priv_data, "preset" , "slow" , 0); /* open it */ ret = avcodec_open2(c, codec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n" , av_err2str(ret)); exit(1); } f = fopen(filename, "wb" ); if (!f) { fprintf(stderr, "Could not open %s\n" , filename); exit(1); } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame\n" ); exit(1); } frame->format = c->pix_fmt; frame->width = c->width; frame->height = c->height; ret = av_frame_get_buffer(frame, 0); if (ret < 0) { fprintf(stderr, "Could not allocate the video frame data\n" ); exit(1); } /* encode 1 second of video */ for (i = 0; i < 25; i++) { fflush(stdout); /* Make sure the frame data is writable. On the first round, the frame is fresh from av_frame_get_buffer() and therefore we know it is writable. But on the next rounds, encode() will have called avcodec_send_frame(), and the codec may have kept a reference to the frame in its internal structures, that makes the frame unwritable. av_frame_make_writable() checks that and allocates a new buffer for the frame only if necessary. */ ret = av_frame_make_writable(frame); if (ret < 0) exit(1); /* Prepare a dummy image. In real code, this is where you would have your own logic for filling the frame. FFmpeg does not care what you put in the frame. */ /* Y */ for (y = 0; y < c->height; y++) { for (x = 0; x < c->width; x++) { frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3; } } /* Cb and Cr */ for (y = 0; y < c->height/2; y++) { for (x = 0; x < c->width/2; x++) { frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2; frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5; } } frame->pts = i; /* encode the image */ encode(c, frame, pkt, f); } /* flush the encoder */ encode(c, NULL, pkt, f); /* Add sequence end code to have a real MPEG file. It makes only sense because this tiny examples writes packets directly. This is called "elementary stream" and only works for some codecs. To create a valid file, you usually need to write packets into a proper file format or protocol; see mux.c. */ if (codec->id == AV_CODEC_ID_MPEG1VIDEO || codec->id == AV_CODEC_ID_MPEG2VIDEO) fwrite(endcode, 1, sizeof (endcode), f); fclose(f); avcodec_free_context(&c); av_frame_free(&frame); av_packet_free(&pkt); return 0; } |
函数介绍
1 查找编码器;还可以使用AVcodec_find_encoder(condec_ID)
const AVCodec* codec = avcodec_find_encoder_by_name(codec_name);//codec_name = "libx264";
函数返回的AVCodec结构体指针,内部包含编码ID,支持的帧率,支持的像素格式,音频采样率,声道,编码档次等
1 /** 2 * AVCodec. 3 */ 4 typedef struct AVCodec { 5 /** 6 * Name of the codec implementation. 7 * The name is globally unique among encoders and among decoders (but an 8 * encoder and a decoder can share the same name). 9 * This is the primary way to find a codec from the user perspective. 10 */ 11 const char *name; 12 /** 13 * Descriptive name for the codec, meant to be more human readable than name. 14 * You should use the NULL_IF_CONFIG_SMALL() macro to define it. 15 */ 16 const char *long_name; 17 enum AVMediaType type; 18 enum AVCodecID id; 19 /** 20 * Codec capabilities. 21 * see AV_CODEC_CAP_* 22 */ 23 int capabilities; 24 uint8_t max_lowres; ///< maximum value for lowres supported by the decoder 25 const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0} 26 const enum AVPixelFormat *pix_fmts; ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1 27 const int *supported_samplerates; ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0 28 const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1 29 #if FF_API_OLD_CHANNEL_LAYOUT 30 /** 31 * @deprecated use ch_layouts instead 32 */ 33 attribute_deprecated 34 const uint64_t *channel_layouts; ///< array of support channel layouts, or NULL if unknown. array is terminated by 0 35 #endif 36 const AVClass *priv_class; ///< AVClass for the private context 37 const AVProfile *profiles; ///< array of recognized profiles, or NULL if unknown, array is terminated by {FF_PROFILE_UNKNOWN} 38 39 /** 40 * Group name of the codec implementation. 41 * This is a short symbolic name of the wrapper backing this codec. A 42 * wrapper uses some kind of external implementation for the codec, such 43 * as an external library, or a codec implementation provided by the OS or 44 * the hardware. 45 * If this field is NULL, this is a builtin, libavcodec native codec. 46 * If non-NULL, this will be the suffix in AVCodec.name in most cases 47 * (usually AVCodec.name will be of the form "<codec_name>_<wrapper_name>"). 48 */ 49 const char *wrapper_name; 50 51 /** 52 * Array of supported channel layouts, terminated with a zeroed layout. 53 */ 54 const AVChannelLayout *ch_layouts; 55 } AVCodec;
2 分配/创建 编码器上下文
作用就是创建上下文,设置编码参数;创建一个上下文之后,就可以设置一些参数了,这个参数会作用到编码器;
AVCodecContext* c = NULL; c = avcodec_alloc_context3(codec);
/* put sample parameters */ c->bit_rate = 400000; //输出码率4M /* resolution must be a multiple of two */ c->width = 1920; //输入的图像宽度,与YUV数据量对应上 c->height = 1080;//输入图像的高 /* frames per second */ c->time_base.num = 1; c->time_base.den = 25; c->framerate.num = 25; //25 FPS c->framerate.den = 1; /* emit one intra frame every ten frames * check frame pict_type before passing frame * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I * then gop_size is ignored and the output of encoder * will always be I frame irrespective to gop_size */ c->gop_size = 10; //GOP 大小是10帧 c->max_b_frames = 1;//每个I、P帧之间最多插入1个B帧 c->pix_fmt = AV_PIX_FMT_YUV420P;//YV 12 planner格式;YVU连续且不存在UV交叉的一种存储格式
3 初始化编码器上下文,在上边第二步创建,设置参数后,初始化上下文,打开编码器
avcodec_open2(c, codec, NULL[用户自定义编码选项]);//const AVCodec* codec; AVCodecContext* c = NULL;
4 创建frame,和package结构 用于承载数据
未压缩的图像放在frame结构体里,编码后的数据放到package结构体里;并不是所有的开源代码里frame存的都是未压缩数据,这个依照具体情况而定;在本博客的测试用例中frame存的是未压缩的YUV数据;有些地方frame存的是264,265数据,使用场景不同,意义也不一样
ffmpeg中的data存储未压缩数据,可以存储平铺模式planer,也可以存(交叉存储packed)的数据;(存储方式的差异主要体现在音频,如左右声道交叉存储,还是每个声道单独存储)
frame = av_frame_alloc();
pkt = av_packet_alloc();
frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame\n"); exit(1); } frame->format = c->pix_fmt;//颜色格式,可设置YUV420P frame->width = c->width; frame->height = c->height; ret = av_frame_get_buffer(frame, 0);//后边每次复用buffer来装YUV之前,要先保证buffer没被占用,是可写的,av_frame_make_writable if (ret < 0) { fprintf(stderr, "Could not allocate the video frame data\n"); exit(1); }
5 读取YUV数据,放到frame的data里边,编码;注意这里的输入输出缓冲都是复用的,是一个 读-编码-输出,串行逻辑
int res_len = fread(buffer,len ,1,fpin); if (res_len > 0) { frame->data[0] = buffer;//Y分量 frame->data[2] =buffer + frame->width * frame->height;//V分量 frame->data[1] = buffer + frame->width * frame->height * 5 / 4;//U 分量 frame->pts += 1;//显示时间戳
//frame 还可以设置该帧要不要编码成关键帧,帧类型等;
/* encode the image */ encode(c, frame, pkt, frle_out_ptr); }
5 AVpackage用来保存编码后的数据,如果H264编码,那这里就是H264的NAL数据,也就是ES流,内部还会保存当前帧的PTS,DTS
编码函数的实现是,送YUV数据,获取Package: avcodec_send_frame(enc_ctx, frame); avcodec_receive_packet(enc_ctx, pkt);
将数据写到文件,解除对package的引用:fwrite(pkt->data, 1, pkt->size, outfile); av_packet_unref(pkt);
typedef struct AVPacket { /** * A reference to the reference-counted buffer where the packet data is * stored. * May be NULL, then the packet data is not reference-counted. */ AVBufferRef *buf; /** * Presentation timestamp in AVStream->time_base units; the time at which * the decompressed packet will be presented to the user. * Can be AV_NOPTS_VALUE if it is not stored in the file. * pts MUST be larger or equal to dts as presentation cannot happen before * decompression, unless one wants to view hex dumps. Some formats misuse * the terms dts and pts/cts to mean something different. Such timestamps * must be converted to true pts/dts before they are stored in AVPacket. */ int64_t pts; /** * Decompression timestamp in AVStream->time_base units; the time at which * the packet is decompressed. * Can be AV_NOPTS_VALUE if it is not stored in the file. */ int64_t dts; uint8_t *data; int size; int stream_index; /** * A combination of AV_PKT_FLAG values */ int flags; /** * Additional packet data that can be provided by the container. * Packet can contain several types of side information. */ AVPacketSideData *side_data; int side_data_elems; /** * Duration of this packet in AVStream->time_base units, 0 if unknown. * Equals next_pts - this_pts in presentation order. */ int64_t duration; int64_t pos; ///< byte position in stream, -1 if unknown /** * for some private data of the user */ void *opaque; /** * AVBufferRef for free use by the API user. FFmpeg will never check the * contents of the buffer ref. FFmpeg calls av_buffer_unref() on it when * the packet is unreferenced. av_packet_copy_props() calls create a new * reference with av_buffer_ref() for the target packet's opaque_ref field. * * This is unrelated to the opaque field, although it serves a similar * purpose. */ AVBufferRef *opaque_ref; /** * Time base of the packet's timestamps. * In the future, this field may be set on packets output by encoders or * demuxers, but its value will be by default ignored on input to decoders * or muxers. */ AVRational time_base; } AVPacket;
6 编码完之后要再次调用下空数据的编码。以防有数据在编码器中未刷新出来
/* flush the encoder */
encode(c, NULL, pkt, fout);
并在编码输出文件结尾添加上mpeg的结束标记 uint8_t endcode[] = { 0, 0, 1, 0xb7 };
7 最后就是释放上下文,释放frame,package,关闭文件
avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);
下边提供一个真实可以编码的例子:(visual studio 2022)
#include <stdio.h> #include <stdlib.h> #include <string.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 }; #define av_err2str(errnum) av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, errnum) static void encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* pkt, FILE* outfile) { int ret; /* send the frame to the encoder */ if (frame) printf("Send frame %3ld\n", frame->pts); ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { fprintf(stderr, "Error sending a frame for encoding\n"); exit(1); } while (ret >= 0) { ret = avcodec_receive_packet(enc_ctx, pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { fprintf(stderr, "Error during encoding\n"); exit(1); } printf("Write packet %3ld (size=%5d)\n", pkt->pts, pkt->size); fwrite(pkt->data, 1, pkt->size, outfile); av_packet_unref(pkt); } } int main(int argc, char** argv) { const char* filename, * codec_name,* filename_in; // const AVCodec* codec; AVCodecContext* c = NULL; int i, ret, x, y; FILE* f; AVFrame* frame; AVPacket* pkt; uint8_t endcode[] = { 0, 0, 1, 0xb7 }; int len = 1920 * 1080 * 3 / 2; uint8_t* buffer = new uint8_t[len]; filename = "D:/Emscripten/cmaketest/20230801230523.264";//编码输出的264,文件没有会自动创建 filename_in = "D:/Emscripten/cmaketest/20230.yuv";//待编码的YUV,YV12;需要有 codec_name = "libx264";//可以自己指定其他编码器 FILE* fpin = fopen(filename_in, "rb+"); if (!fpin) { fprintf(stderr, "Codec '%s' not open\n",filename_in); exit(1); } //查找编码器;还可以使用AVcodec_find_encoder(condec_ID) codec = avcodec_find_encoder_by_name(codec_name); if (!codec) { fprintf(stderr, "Codec '%s' not found\n", codec_name); exit(1); } c = avcodec_alloc_context3(codec); if (!c) { fprintf(stderr, "Could not allocate video codec context\n"); exit(1); } pkt = av_packet_alloc(); if (!pkt) exit(1); /* put sample parameters */ c->bit_rate = 400000; /* resolution must be a multiple of two */ c->width = 1920; c->height = 1080; /* frames per second */ c->time_base.num = 1; c->time_base.den = 25;//时间基1/25 c->framerate.num = 25; //fps=25 c->framerate.den = 1; /* emit one intra frame every ten frames * check frame pict_type before passing frame * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I * then gop_size is ignored and the output of encoder * will always be I frame irrespective to gop_size */ c->gop_size = 10; c->max_b_frames = 1; c->pix_fmt = AV_PIX_FMT_YUV420P;//YV 12 格式 if (codec->id == AV_CODEC_ID_H264) av_opt_set(c->priv_data, "preset", "slow", 0); /* open it */ ret = avcodec_open2(c, codec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); exit(1); } f = fopen(filename, "wb"); if (!f) { fprintf(stderr, "Could not open %s\n", filename); exit(1); } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame\n"); exit(1); } frame->format = c->pix_fmt; frame->width = c->width; frame->height = c->height; ret = av_frame_get_buffer(frame, 0); if (ret < 0) { fprintf(stderr, "Could not allocate the video frame data\n"); exit(1); } /* encode 1 second of video */ while (!feof(fpin)) { fflush(stdout); /* Make sure the frame data is writable. On the first round, the frame is fresh from av_frame_get_buffer() and therefore we know it is writable. But on the next rounds, encode() will have called avcodec_send_frame(), and the codec may have kept a reference to the frame in its internal structures, that makes the frame unwritable. av_frame_make_writable() checks that and allocates a new buffer for the frame only if necessary. */ ret = av_frame_make_writable(frame); if (ret < 0) exit(1); /* Prepare a dummy image. In real code, this is where you would have your own logic for filling the frame. FFmpeg does not care what you put in the frame. */ /* Y V,U*/ int res_len = fread(buffer,len ,1,fpin); if (res_len > 0) {//这里可以直接读文件,我是用buffer从中间倒了一手,只要数据对得上,没必要借助中间buffer读数据 frame->data[0] = buffer; frame->data[2] =buffer + frame->width * frame->height; frame->data[1] = buffer + frame->width * frame->height * 5 / 4; frame->pts += 1;//时间基 1/25 /* encode the image */ encode(c, frame, pkt, f); } } /* flush the encoder */ encode(c, NULL, pkt, f); if (codec->id == AV_CODEC_ID_MPEG1VIDEO || codec->id == AV_CODEC_ID_MPEG2VIDEO) fwrite(endcode, 1, sizeof(endcode), f); fclose(f); avcodec_free_context(&c); av_frame_free(&frame); av_packet_free(&pkt); delete[] buffer; fclose(fpin); return 0; } }
效果:
264数据用elecard打开:
这个是我修改 gop =25;无B帧的情况,编码后的数据
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2022-09-20 git主分支覆盖子分支
2022-09-20 IOS摄像头采和显示