H.264/AVC 也可以叫做 H.264/MPEG-4 part 10 AVC,这是一个联合名字,H.264 冠的是 ITU-T 的名称,AVC(Advanced Video Coding) 冠的是 ISO-IEC 的名字。ITU-T 是国际电信标准化部门。ISO-IEC是国际标准化组织-国际电工委员会。在 2001 年的 12 月, ITU-T 的 VCEG(Video Coding Experts Group)和 ISO-IEC 的 MPEG(Moving Picture Experts Group)联合成立了一个新的机构叫 JVT(Joint Video Team),就是这个新的组织 JVT 于 2003 年 3 月 发布了 H264/AVC 视频编码标准。
H.264/AVC 是迄今为止视频录制、压缩和分发的最常用格式。截至 2019 年 9 月,已有 91% 的视频开发人员使用了该格式。H.264/AVC 提供了明显优于以前任何标准的压缩性能。H.264/AVC 因其是蓝光盘的其中一种编解码标准而著名,所有蓝光盘播放器都必须能解码 H.264/AVC。
视频播放的本质是展示一张张图像,如果一秒钟至少连续播放 24 张图像,那么人眼看到的就是连续的视频画面。通过手机或者电脑在线观看视频,首先需要将这些图像通过网络传输到你的手机或者电脑,这需要考虑的一个问题就是视频对网络带宽的占用情况。H.264/AVC 作为一个视频压缩标准,其主要使命就是降低视频的带宽占用,提高传输效率。H.264/AVC 的压缩比可以达到至少是 100:1。
是否真的需要对图像进行压缩?我们知道一张大小为 1920x1080 用 yuv420p 像素格式表示的一张图像的大小是 1920 * 1080 * 1.5 = 3110400 字节(yuv420p 像素格式每个像素占用 1.5 字节),那么一段 10 秒钟 30fps 的 1080p(1920x1080)原始视频的大小就是 3110400 * 30 * 10 = 933120000 字节,大约是 889.89MB,可以看出来原始视频的体积非常大。因此需要使用视频编码技术对原始视频进行压缩来降低数据传输和存储的成本。
$ ffmpeg -s 640x360 -pix_fmt yuv420p -i IMG_1459_yuvj420p640x360fps30.yuv -c:v libx264 out.h264
在我本地 H.264 编码器默认就是 libx264,所以 -c:v libx264 也可省略不写。排在最前面的编码器即默认编码器:
$ ffmpeg -encoders | grep 264
V..... libx264 libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264)
V..... libx264rgb libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 RGB (codec h264)
V..... h264_videotoolbox VideoToolbox H.264 Encoder (codec h264)
查看 libx264 支持的像素格式:
$ ffmpeg -h encoder=libx264
Encoder libx264 [libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10]:
Supported pixel formats: yuv420p yuvj420p yuv422p yuvj422p yuv444p yuvj444p nv12 nv16 nv21 yuv420p10le yuv422p10le yuv444p10le nv20le gray gray10le
H.264 视频编码和 AAC 音频编码流程是类似的,H.264 视频编码使用的是编码器 x264,在 FFmpeg 中的名称是 libx264(libx264 并没有默认内置到 FFmpeg 中,我们是在编译 FFmpeg 时手动将通过 homebrew 安装到本地的 libx264 内置到 FFmpeg 中的)。
首先需要导入我们需要用到的库,主要用到 FFmpeg 两个库 libavcodec 和 libavutil:
macx {
INCLUDEPATH += /usr/local/ffmpeg/include
LIBS += -L/usr/local/ffmpeg/lib -lavcodec \
-lavutil
}
1、获取编码器
codec = avcodec_find_encoder_by_name("libx264");
也可以使用 ID 的方式获取编码器,使用使用 ID 的方式获取到的是本地默认编码器,在我本地默认 H.264 编码器就是 libx264,所以使用下面方式获取的编码器和上面使用 name 获取到的编码器是同一个编码器,都是 libx264(具体情况需要查看本地环境):
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
前面对音频进 AAC 编码时,AAC 编码器对数据的采样格式是有要求的,比如 libfdk_aac 要求采样格式是 s16 整型,同样的 H.264 编码库 libx264 对输入数据像素格式也有要求,虽然 avcodec_open2 函数内部也会对像素格式进行检查,但是建议提前检查输入像素格式:
if (!check_pix_fmt(codec, in.pixFmt)) {
qDebug() << "unsupported pixel format" << av_get_pix_fmt_name(in.pixFmt);
return;
}
static int check_pix_fmt(const AVCodec *codec, enum AVPixelFormat pixFmt)
{
const enum AVPixelFormat *p = codec->pix_fmts;
while (*p != AV_PIX_FMT_NONE) {
if (*p == pixFmt) return 1;
p++;
}
return 0;
}
codec->pix_fmts 中存放的是当前编码器支持的像素格式。AV_PIX_FMT_NONE 是一个边界标识,用于判断是否遍历结束。
2、创建编码上下文
ctx = avcodec_alloc_context3(codec);
设置编码上下文参数:
ctx->width = in.width;
ctx->height = in.height;
ctx->pix_fmt = in.pixFmt;
// 设置帧率(1秒钟显示的帧数是in.fps)
ctx->time_base = {1, in.fps};
3、打开编码器
ret = avcodec_open2(ctx, codec, nullptr);
可以通过参数 options 设置一些编码器特有参数。
4、创建 AVFrame
frame = av_frame_alloc();
av_frame_alloc 仅仅是为 AVFrame 分配空间,数据缓冲区 frame->data[0] 需要我们调用函数 av_frame_get_buffer 来创建。调用函数 av_frame_get_buffer 前设置 frame 的 width、height 和 format, 利用 width、height 和 format 可算出一帧图像大小, frame->data[0] 指向的堆空间其实就是一帧图像的大小:
frame->width = ctx->width;
frame->height = ctx->height;
frame->format = ctx->pix_fmt;
ret = av_frame_get_buffer(frame, 1);
5、创建 AVPacket
pkt = av_packet_alloc();
6、打开文件,从文件读取数据到 AVFrame
inFile.open(QFile::ReadOnly);
// 一帧图像的大小
int image_size = av_image_get_buffer_size(in.pixFmt, in.width, in.height, 1);
inFile.read((char *)frame->data[0], image_size);
7、解码
// 返回 0:编码操作正常完成;返回负数:中途出现了错误
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, QFile &outFile) {
// 发送数据到编码器
int ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof (errbuf));
qDebug() << "avcodec_send_frame error" << errbuf;
return ret;
}
// 不断从编码器中取出编码后的数据
while (true) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // 继续读取数据到 frame,然后送到编码器
return 0;
} else if (ret < 0) { // 其他错误
return ret;
}
// 成功从编码器拿到编码后的数据,将编码后的数据写入文件
outFile.write((char *) pkt->data, pkt->size);
// 释放 pkt 内部的资源
av_packet_unref(pkt);
}
}
8、释放资源
// 关闭文件
inFile.close();
outFile.close();
// 释放资源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&ctx);
最后运行我们的程序进行编码,发现 Qt 控制台会打印如下错误,是因为我们没有设置帧序号导致的:
[libx264 @ 0x7fd6f0061c00] non-strictly-monotonic PTS
-9223372036854775808
解决办法:
// 帧序号初始化为 0
frame->pts = 0;
// 设置帧序号,每编码一帧,帧序号加 1
frame->pts++;
然后我们使用 ffplay 播放我们压缩后的 h264 文件,发现压缩后视频是有问题的:

在终端使用同样的编码器和同样的输入参数编码生成一个 h264 文件,通过对比发现代码生成的 h264 文件要比在终端生成的 h264 文件大不少:
$ ls -al
-rw-r--r-- 1 mac staff 110592000 Mar 30 16:28 in_640x480_yuv420p.yuv
-rw-r--r-- 1 mac staff 583538 Apr 12 09:59 out_640x480_yuv420p_code.h264
-rw-r--r-- 1 mac staff 437271 Apr 12 09:55 out_640x480_yuv420p_terminal.h264
通过检查发现问题产生的原因是 frame->data 缓冲区大小超过了一帧图像大小:
// 打印 frame->data:
qDebug() << frame->data[0] << frame->data[1] << frame->data[2];
// 控制台输出:
0x7fc3001a2000 0x7fc3001ed020 0x7fc3001ffc40
// 计算各平面大小:
Y平面大小 = frame->data[1] - frame->data[0] = 0x7fc3001ed020 - 0x7fc3001a2000 = 307232 字节
U平面大小 = frame->data[2] - frame->data[1] = 0x7fc3001ffc40 - 0x7fc3001ed020 = 76832 字节
// 正确的各平面大小:
Y平面大小 = 640 * 480 * 1 = 307200 字节
U平面大小 = (640 / 2) * (480 / 2) * 1 = 76800 字节
V平面大小 = (640 / 2) * (480 / 2) * 1 = 76800 字节
发现 frame 数据缓冲区大小比我们预期的要大。查看av_frame_get_buffer 源码,是因为函数 av_frame_get_buffer 内部分配数据缓冲区空间时增加了 32 字节的 plane_padding 导致的。可以换成函数 av_image_alloc 或者函数 av_image_fill_arrays 分配数据缓冲区空间:
ret = av_image_alloc(frame->data, frame->linesize, in.width, in.height, in.pixFmt, 1);
// 最后不要忘记释放数据缓冲区
av_freep(&frame->data[0]);
// 或者:
ret = av_image_fill_arrays(frame->data, frame->linesize, buf, in.pixFmt, in.width, in.height, 1);
完整示例代码:
h264_encode.pro:
macx {
INCLUDEPATH += /usr/local/ffmpeg/include
LIBS += -L/usr/local/ffmpeg/lib -lavcodec \
-lavutil
}
ffmpegutils.h:
#ifndef FFMPEGUTILS_H
#define FFMPEGUTILS_H
#include <QObject>
extern "C" {
#include <libavutil/avutil.h>
}
typedef struct {
const char *filename;
int width;
int height;
AVPixelFormat pixFmt;
int fps;
} VideoEncodeSpec;
class FFmpegUtils : public QObject
{
Q_OBJECT
public:
explicit FFmpegUtils(QObject *parent = nullptr);
static void h264Encode(VideoEncodeSpec &in, const char *outFilename);
signals:
};
#endif // FFMPEGUTILS_H
ffmpegutils.m:
#include "ffmpegutils.h"
#include <QDebug>
#include <QFile>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
}
#define ERRBUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf))
FFmpegUtils::FFmpegUtils(QObject *parent) : QObject(parent)
{
}
int check_pix_fmt(const AVCodec *codec, enum AVPixelFormat pix_fmt)
{
const enum AVPixelFormat *p = codec->pix_fmts;
while (*p != AV_PIX_FMT_NONE) {
if (*p == pix_fmt) return 1;
p++;
}
return 0;
}
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, QFile &outFile)
{
int ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
ERRBUF(ret);
qDebug() << "error sending the frame to the codec: " << errbuf;
return ret;
}
while (true) {
// 从编码器中获取编码后的数据
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if (ret < 0) {
ERRBUF(ret);
qDebug() << "error encode audio frame: " << errbuf;
return ret;
}
outFile.write((const char *)pkt->data, pkt->size);
av_packet_unref(pkt);
}
return 0;
}
void FFmpegUtils::h264Encode(VideoEncodeSpec &in, const char *outFilename)
{
int ret = 0;
// 编码器
AVCodec *codec = nullptr;
// 上下文
AVCodecContext *ctx = nullptr;
// 用来存放编码前的数据(yuv)
AVFrame *frame = nullptr;
// 用来存放编码后的数据(h264)
AVPacket *pkt = nullptr;
QFile inFile(in.filename);
QFile outFile(outFilename);
// 一帧图片的大小
int image_size = av_image_get_buffer_size(in.pixFmt, in.width, in.height, 1);
// 输入缓冲区 方式二
uint8_t *buf = nullptr;
// 查找编码器
codec = avcodec_find_encoder_by_name("libx264");
if (!codec) {
qDebug() << "encoder libx264 not found";
return;
}
// 检查编码器是否支持该编码格式
if (!check_pix_fmt(codec, in.pixFmt)) {
qDebug() << "unsupported pixel format: " << av_get_pix_fmt_name(in.pixFmt);
goto end;
}
// 创建编码上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
qDebug() << "could not allocate video codec context";
return;
}
// 设置 ctx 参数
ctx->width = in.width;
ctx->height = in.height;
ctx->pix_fmt = in.pixFmt;
// 设置帧率 1秒钟显示多少帧
ctx->time_base = {1, in.fps};
// 打开编码器
ret = avcodec_open2(ctx, codec, nullptr);
if (ret < 0) {
ERRBUF(ret);
qDebug() << "could not open codec: " << errbuf;
goto end;
}
// 创建packet
pkt = av_packet_alloc();
if (!pkt) {
qDebug() << "could not allocate audio packet";
goto end;
}
// 创建frame
frame = av_frame_alloc();
if (!frame) {
qDebug() << "could not allocate audio frame";
goto end;
}
// 保证 frame 里就是一帧 yuv 数据
frame->width = ctx->width;
frame->height = ctx->height;
// format 是通用的
frame->format = ctx->pix_fmt;
frame->pts = 0;
// 创建输入缓冲区 方法一
ret = av_image_alloc(frame->data, frame->linesize, in.width, in.height, in.pixFmt, 1);
if (ret < 0) {
ERRBUF(ret);
qDebug() << "could not allocate audio data buffers: " << errbuf;
goto end;
}
/*
// 利用width、height、format创建frame的数据缓冲区,利用width、height、format可以算出一帧大小
ret = av_frame_get_buffer(frame, 1);
if (ret < 0) {
ERRBUF(ret);
qDebug() << "could not allocate audio data buffers: " << errbuf;
goto end;
}
*/
/*
// 创建输入缓冲区 方法二
buf = (uint8_t *)av_malloc(image_size);
ret = av_image_fill_arrays(frame->data, frame->linesize, buf, in.pixFmt, in.width, in.height, 1);
if (ret < 0) {
ERRBUF(ret);
qDebug() << "could not allocate audio data buffers: " << errbuf;
goto end;
}
*/
// 打开文件
if (!inFile.open(QFile::ReadOnly)) {
qDebug() << "open file failure: " << in.filename;
goto end;
}
if (!outFile.open(QFile::WriteOnly)) {
qDebug() << "open file failure: " << outFilename;
goto end;
}
// 读取数据到 frame 中
while (inFile.read((char *)frame->data[0], image_size) > 0) {
// 编码
if (encode(ctx, frame, pkt, outFile) < 0) {
goto end;
}
// 设置帧的序号
frame->pts++;
}
// 刷新缓冲区,刷出缓冲区剩余数据
encode(ctx, nullptr, pkt, outFile);
end:
inFile.close();
outFile.close();
if (frame) {
av_freep(&frame->data[0]);
av_frame_free(&frame);
}
av_packet_free(&pkt);
avcodec_free_context(&ctx);
}
函数调用:
VideoEncodeSpec spec;
spec.filename = "/users/mac/Downloads/pic/in_640x480_yuv420p.yuv";
spec.width = 640;
spec.height = 480;
spec.pixFmt = AV_PIX_FMT_YUV420P;
spec.fps = 25;
FFmpegUtils::h264Encode(spec, "/users/mac/Downloads/pic/out_640x480_yuv420p_code.h264");
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po