最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)_雷霄骅的博客-CSDN博客_ffmpeg 编码器
初学音视频、ffmpeg。根据雷神的例子跑起来,调用libavcodec将YUV像素数据(YUV420P)编码为H.264码流,H.265为(HEVC)。
视频编码:
视频编码方式就是指通过特定的压缩技术,将某个视频格式的文件转换成另一种视频格式文件的方式。视频编码格式常见到的有:MPEG-2 TS、Divx、Xvid、H.264、WMV-HD和VC-1。
原始的图像和声音是需要占用很大的存储空间和带宽的,不适合运输和传送(例如例子的yuv数据),所以我们需要对原始图像和声音加工,压缩得更小。就是图像和声音的压缩方法。

编码流程:
示例里编码完的ds.h264 文件在vlc 里播放可以看到分辨率为 480x272

现在研究把480x272 的输分辨率改为 320x270,需要使用以下函数:
FFmpeg中的 sws_scale() 函数主要是用来做视频像素格式和分辨率的转换。
// 初始化
struct SwsContext *sws_ctx(
int srcW, /* 输入图像的宽度 */
int srcH, /* 输入图像的宽度 */
enum AVPixelFormat srcFormat, /* 输入图像的像素格式 */
int dstW, /* 输出图像的宽度 */
int dstH, /* 输出图像的高度 */
enum AVPixelFormat dstFormat, /* 输出图像的像素格式 */
int flags,/* 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR */
SwsFilter *srcFilter, /* 输入图像的滤波器信息, 若不需要传NULL */
SwsFilter *dstFilter, /* 输出图像的滤波器信息, 若不需要传NULL */
const double *param /* 特定缩放算法需要的参数(?),默认为NULL */
);
// 转换的函数
sws_scale(sws_ctx, src_frame->data, src_frame->linesize,
0, height, //源图像的高
dst_frame->data, dst_frame->linesize);
1.参数 SwsContext *c, 转换格式的上下文。也就是 sws_getContext 函数返回的结果。
2.参数 const uint8_t *const srcSlice[], 输入图像的每个颜色通道的数据指针。其实就是解码后的 AVFrame中的data[]数组。因为不同像素的存储格式不同,所以srcSlice[]维数也有可能不同。
以YUV420P为例,它是planar格式,它的内存中的排布如下:
YYYYYYYY UUUU VVVV
使用FFmpeg解码后存储在AVFrame的data[]数组中时:
data[0]——-Y分量, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8……
data[1]——-U分量, U1, U2, U3, U4……
data[2]——-V分量, V1, V2, V3, V4……
linesize[]数组中保存的是对应通道的数据宽度 ,
linesize[0]——-Y分量的宽度
linesize[1]——-U分量的宽度
linesize[2]——-V分量的宽度
而RGB24,它是packed格式,它在data[]数组中则只有一维,它在存储方式如下:
data[0]: R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4……
这里要特别注意,linesize[0]的值并不一定等于图片的宽度,有时候为了对齐各解码器的CPU,实际尺寸会大于图片的宽度,这点在我们编程时(比如OpengGL硬件转换/渲染)要特别注意,否则解码出来的图像会异常。
3.参数const int srcStride[],输入图像的每个颜色通道的跨度。.也就是每个通道的行字节数,对应的是解码后的AVFrame中的linesize[]数组。根据它可以确立下一行的起始位置,不过stride和width不一定相同,这是因为:
a.由于数据帧存储的对齐,有可能会向每行后面增加一些填充字节这样 stride = width + N;
b.packet色彩空间下,每个像素几个通道数据混合在一起,例如RGB24,每个像素3字节连续存放,因此下一行的位置需要跳过3*width字节。
4.参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。
5.参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个颜色通道数据指针,每个颜色通道行字节数)
// 释放sws_scale
sws_freeContext(sws_ctx);
在雷神代码基础上修改主要加了以上函数,加了一些不懂函数的注释。现在把代码贴出来,主要要区分开输入数据和输出数据:
#include <QApplication>
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif
//test different codec
#define TEST_H264 1
#define TEST_HEVC 0// H265
int main(int argc, char* argv[])
{
AVCodec *pCodec;
AVCodecContext *pCodecCtx= NULL;
int i, ret, got_output;
FILE *fp_in;
FILE *fp_out;
// AVFrame *src_frame;//存储一帧未编码的像素数据。
AVFrame *src_frame, *dst_frame;// 输入和输出
AVPacket pkt;//存储一帧压缩编码数据。
int y_size;
int framecnt=0;
char filename_in[]="../ds_480x272.yuv";
static struct SwsContext *img_convert_ctx;
#if TEST_HEVC
AVCodecID codec_id=AV_CODEC_ID_HEVC;
char filename_out[]="ds.hevc";
#else
AVCodecID codec_id=AV_CODEC_ID_H264;
char filename_out[]="ds.h264";
#endif
int src_w = 480, src_h = 272;
int dst_w = 320, dst_h = 270;
int framenum = 100;
avcodec_register_all();//注册所有编解码器相关的组件。av_register_all():注册所有的编解码器,复用/解复用器等等组件
pCodec = avcodec_find_encoder(codec_id);//查找ID值为AV_CODEC_ID_H264的AAC编码器
if (!pCodec) {
printf("Codec not found\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec); // 为AVCodecContext分配内存 创建AVCodecContext结构体。AVCodecContext中很多的参数是编码的时候使用
if (!pCodecCtx) {
printf("Could not allocate video codec context\n");
return -1;
}
pCodecCtx->bit_rate = 400000;//平均比特率 目标的码率,即采样的码率;显然,采样码率越大,视频大小越大
pCodecCtx->width = dst_w;//如果是视频的话,代表宽和高 编码目标的视频帧大小,以像素为单位
pCodecCtx->height = dst_h;
//帧率的基本单位,我们用分数来表示,
//用分数来表示的原因是,有很多视频的帧率是带小数的eg:NTSC 使用的帧率是29.97
pCodecCtx->time_base.num=1; //根据该参数,可以把PTS转化为实际的时间(单位为秒s)
pCodecCtx->time_base.den=25;
pCodecCtx->gop_size = 10;//一组图片中的图片数
//两个非B帧之间允许出现多少个B帧数
//设置0表示不使用B帧
//b 帧越多,图片越小
pCodecCtx->max_b_frames = 1;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;//像素格式 也就是说采用什么样的色彩空间来表明一个像素点
//设置显示的率
pCodecCtx->framerate = {10, 1};
//量化参数设置(影响视频清晰度) 越小越清晰
pCodecCtx->qmax = 51;
pCodecCtx->qmin = 10;
if (codec_id == AV_CODEC_ID_H264)
av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开编码器。
printf("Could not open codec\n");
return -1;
}
src_frame = av_frame_alloc();//分配AVFrame AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM
if (!src_frame) {
printf("Could not allocate video frame\n");
return -1;
}
src_frame->format = pCodecCtx->pix_fmt; //帧的格式
src_frame->width = src_w;
src_frame->height = src_h;//视频帧宽和高(1920x1080,1280x720...)
dst_frame = av_frame_alloc();
dst_frame->width = dst_w;
dst_frame->height = dst_h; //初始化一个SwsContext
img_convert_ctx = sws_getContext(src_frame->width, src_frame->height, \
pCodecCtx->pix_fmt, dst_frame->width, dst_frame->height, \
pCodecCtx->pix_fmt, 0, nullptr, nullptr, nullptr);
// int dst_bytes_num = avpicture_get_size(pCodecCtx->pix_fmt, dst_frame->width, dst_frame->height);//计算这个格式的图片,需要多少字节来存储
// uint8_t* dst_buff = (uint8_t *)av_malloc(dst_bytes_num * sizeof(uint8_t));//申请空间来存放图片数据。包含源数据和目标数据
// 前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,
// 而该类型的指针指向的内存还没分配。这里把av_malloc得到的内存和AVFrame关联起来。
// 当然,其还会设置AVFrame的其他成员
// avpicture_fill((AVPicture *)dst_frame, dst_buff, pCodecCtx->pix_fmt, dst_frame->width, dst_frame->height);//ffmpeg4.2.2中就已经被抛弃了, 取而代之的是av_image_fill_arrays()
//以上注释的代码也能实现 av_image_alloc
av_image_alloc(dst_frame->data, dst_frame->linesize, dst_frame->width, dst_frame->height,
pCodecCtx->pix_fmt, 16);
ret = av_image_alloc(src_frame->data, src_frame->linesize, src_frame->width, src_frame->height,
pCodecCtx->pix_fmt, 16);//按照指定的宽、高、像素格式来分析图像内存
// pointers[4]:保存图像通道的地址。如果是RGB,则前三个指针分别指向R,G,B的内存地址。第四个指针保留不用
// linesizes[4]:保存图像每个通道的内存对齐的步长,即一行的对齐内存的宽度,此值大小等于图像宽度。
// w: 要申请内存的图像宽度。
// h: 要申请内存的图像高度。
// pix_fmt: 要申请内存的图像的像素格式。
// align: 用于内存对齐的值。
// 返回值:所申请的内存空间的总大小。如果是负值,表示申请失败。
if (ret < 0) {
printf("Could not allocate raw picture buffer\n");
return -1;
}
//Input raw data
fp_in = fopen(filename_in, "rb");//使用给定的模式 mode 打开 filename 所指向的文件。 rb+读写打开一个二进制文件,允许读数据。
if (!fp_in) {
printf("Could not open %s\n", filename_in);
return -1;
}
//Output bitstream
fp_out = fopen(filename_out, "wb");//wb 只写打开或新建一个二进制文件;只允许写数据。
if (!fp_out) {
printf("Could not open %s\n", filename_out);
return -1;
}
y_size = src_frame->width * src_frame->height;
//Encode
for (i = 0; i < framenum; i++) {
av_init_packet(&pkt);//设置默认值
pkt.data = NULL; // packet data will be allocated by the encoder
pkt.size = 0; //从给定流 fp_in 读取数据到 src_frame->data[0] 所指向的数组中。
//Read raw YUV data
if (fread(src_frame->data[0],1,y_size,fp_in)<= 0|| // Y
fread(src_frame->data[1],1,y_size/4,fp_in)<= 0|| // U
fread(src_frame->data[2],1,y_size/4,fp_in)<= 0){ // V
return -1;
}else if(feof(fp_in)){//测试给定流 fp_in 的文件结束标识符。
break;
}
sws_scale(img_convert_ctx, (uint8_t const * const *)src_frame->data,
src_frame->linesize, 0, src_frame->height, dst_frame->data, dst_frame->linesize);
src_frame->pts = i;
/* encode the image */
ret = avcodec_encode_video2(pCodecCtx, &pkt, dst_frame, &got_output);//编码一帧数据。要传转换完的 dst_frame
if (ret < 0) {
printf("Error encoding frame\n");
return -1;
}
if (got_output) {
printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
framecnt++;
fwrite(pkt.data, 1, pkt.size, fp_out);//把 pkt.data 所指向的数组中的数据写入到给定流 fp_out 中
av_free_packet(&pkt);//清空pkt中data以及buf的内容,并没有把pkt的指针清空
}
}
//Flush Encoder
for (got_output = 1; got_output; i++) {
ret = avcodec_encode_video2(pCodecCtx, &pkt, NULL, &got_output);
if (ret < 0) {
printf("Error encoding frame\n");
return -1;
}
if (got_output) {
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",pkt.size);
fwrite(pkt.data, 1, pkt.size, fp_out);
av_free_packet(&pkt);
}
}
fclose(fp_out);//关闭流 fp_out。刷新所有的缓冲区。
avcodec_close(pCodecCtx);//关闭编码器
av_free(pCodecCtx);//释放已分配av_malloc() AVCodecContext
av_freep(&src_frame->data[0]);//释放并清理指针
av_frame_free(&src_frame);//释放AVFrame
av_frame_free(&dst_frame);//释放AVFrame
sws_freeContext(img_convert_ctx);
return 0;
}
下面是转换完的视频:

参考文档:
FFmpeg编码基础流程_zhaodb_的博客-CSDN博客_ffmpeg编码流程 FFmpeg: FFmepg中的sws_scale() 函数分析 - 夜行过客 - 博客园
图像视频编码和FFmpeg(3)-----用FFmpeg进行图像格式转换和AVFrame简介 avpicture_fill - bw_0927 - 博客园ffmpeg的API函数用法 :sws_scale函数的用法-具体应用 - 怀想天空2013 - 博客园图像视频编码和FFmpeg(3)-----用FFmpeg进行图像格式转换和AVFrame简介 avpicture_fill - bw_0927 - 博客园
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我正在使用ruby1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
//1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p
📢博客主页:https://blog.csdn.net/weixin_43197380📢欢迎点赞👍收藏⭐留言📝如有错误敬请指正!📢本文由Loewen丶原创,首发于CSDN,转载注明出处🙉📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨文章预览:一.分辨率(Resolution)1、工业相机的分辨率是如何定义的?2、工业相机的分辨率是如何选择的?二.精度(Accuracy)1、像素精度(PixelAccuracy)2、定位精度和重复定位精度(RepeatPrecision)三.公差(Tolerance)四.课后作业(Post-ClassExercises)视觉行业的初学者,甚至是做了1~2年
我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope
我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功
我有以下内容:text.gsub(/(lower)(upper)/,'\1\2')我可以将\2替换为大写吗?类似于:sed-e's/\(abc\)/\U\1/'这在Ruby中可行吗? 最佳答案 查看gsub文档:str.gsub(模式){|匹配|block}→new_str在block形式中,当前匹配字符串作为参数传入,$1、$2、$`、$&、$'等变量将被适当设置。block返回的值将替换为每次调用的匹配项。"alowerupperb".gsub(/(lower)(upper)/){|s|$1+""+$2.upcase}
我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby-vips的github页面上的链接,我们将不胜感激!如果有ruby-