ffmpeg编解码流程 哪种编码速度最快

好几年没上CNBLOGS了,
& & & & & & & &最近在研究FFmpeg编解码,一个人研究感到很寂寞,所以想通过博客来和大家分享和交流,呵呵。
最近研究的主题是: ANDROID手机同屏技术:
& & & & & & & & & & & & & 需要用到ANDROID截屏,然后通过FFmpeg将BMP编码成H264,然后通过RTP协议发送出去。接收端为手机,要求延时小,清晰流畅,并且手机还可以反控ANDROID主机。
& & & & & & & & & & & & & &我的想法是先在VS2010上将整个原理通路打通,先在PC上调试和DEMO,然后再移植。直接进行ANDROID的NDK编程,那个开发效率和调试效率太低啦。
& & & & & & & & & & & & & &目前的进展:基于VS2010
& & & & & & & & & & & & & & & & & & & & & & 可以连续截屏,并且还可以将截屏的N张BMP图片通过FFmpeg编码成H264.&
& & & & & & & & & & & & & & & & & & & & & & &还需解决的问题:
                1.&内存泄漏。(头大,C++程序员的永久之痛!)
                2.发现FFmpeg的编码效率不太高,编码慢,只能基本满足15帧每秒。如果移植到ARM平台,由于CPU资源有限,恐怕效率更低。如果实在不能解决,到时候再考虑利用ARM的H264硬件H264编码.
& & & & & & & & & & & & & & & & & & & & & & &3. 现在是截屏的BMP分辨率是,编码产生的H264文件的分辨率也是,想缩放成,这样通过RTP传送时,占用带宽少。
& & & & & & & & & & & & & & & & & & & & & & &这个问题通过SWC_SCALE函数应该好解决。
& & & & & & & & & & & & & &
& & & & & & & & & & & & & 目前流程:
& & & & & & & & & & & & & & & & & & & & & 截屏,保存为若干个BMP文件----&RGB转YUV420P-----FFmpeg编码--------&H264文件。
& & & & & & & & & & & & & & & &
阅读(...) 评论()注:本文属于转载译文,原文地址:http://blog.csdn.net/vblittleboy/article/details/8982857。
英文地址:。内容有一定出入,但是可以借鉴学习。
x264是一个 H.264/MPEG4 AVC 编码器,本指南将指导新手如何创建高质量的H.264视频。
对于普通用户通常有两种码率控制模式:crf(Constant Rate Factor)和Two pass ABR。码率控制是一种决定为每一个视频帧分配多少比特数的方法,它将决定文件的大小和质量的分配。
如果你在编译和安装libx264 方面需要帮助,请查看ffmpeg和x264编译指南:
http://ffmpeg.org/trac/ffmpeg/wiki/CompilationGuide
crf(Constant Rate Factor):
该方法在输出文件的大小不太重要的时候,可以使整个文件达到特定的视频质量。该编码模式在单遍编码模式下提供了最大的压缩效率,每一帧可以按照要求的视频质量去获取它需要的比特数。不好的一面是,你不能获取一个特定大小的视频文件,或者说将输出位率控制在特定的大小上。
1 选择一个CRF值
量化比例的范围为0~51,其中0为无损模式,23为缺省值,51可能是最差的。该数字越小,图像质量越好。从主观上讲,18~28是一个合理的范围。18往往被认为从视觉上看是无损的,它的输出视频质量和输入视频一模一样或者说相差无几。但从技术的角度来讲,它依然是有损压缩。
若Crf值加6,输出码率大概减少一半;若Crf值减6,输出码率翻倍。通常是在保证可接受视频质量的前提下选择一个最大的Crf值,如果输出视频质量很好,那就尝试一个更大的值,如果看起来很糟,那就尝试一个小一点值。
注释:本文所提到的量化比例只适用于8-bitx264(10-bit x264的量化比例 为0~63),你可以使用x264 --help命令在Output bit depth选项查看输出位深,在各种版本中,8bit是最常见的。
2 选择一个预设
预设是一系列参数的集合,这个集合能够在编码速度和压缩率之间做出一个权衡。一个编码速度稍慢的预设会提供更高的压缩效率(压缩效率是以文件大小来衡量的)。这就是说,假如你想得到一个指定大小的文件或者采用恒定比特率编码模式,你可以采用一个较慢的预设来获得更好的质量。同样的,对于恒定质量编码模式,你可以通过选择一个较慢的预设轻松地节省比特率。
如果你很有耐心,通常的建议是使用最慢的预设。目前所有的预设按照编码速度降序排列为: ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow,placebo
缺省预设为medium,请忽略 placebo因为它是毫无用处的(参看下面的问答)。你可以使用--preset来查看预设列表,也可以通过x264 --fullhelp来查看预设所采用的参数配置。
你可以基于输入内容的独特性通过使用--tune来改变参数设置。当前的 tune包括:film,animation,grain,stillimage,psnr,ssim,fastdecode,zerolantency。假如你的压制内容是动画,你可以使用animation,或者你想保留纹理,那就是用grain。如果你不确定使用哪个选项或者说你的输入与所有的tune皆不匹配,你可以忽略--tune 选项。你可以使用--tune来查看tune列表,也可以通过x264 --fullhelp来查看tune所采用的参数配置。
另外一个可选的参数是--profile,它可以将你的输出限制到一个特定的 H.264 profile,该选项可以被忽略除非你的播放设备只支持某种profile。当的所有profile 包括:baseline,main.high,high10,high422,high444 。注意使用--profile选项和无损编码是不兼容的。
如下所示,作为一种快捷方式,你可以通过不声明 preset和tune得内容来为ffmpeg罗列所有可能的内部preset和tune。
ffmpeg -i input -c:v libx264 -preset -tune dummy.mp4
3 使用你的预设
一旦你选择了一个预设,请把它应用到你的剩余的尚未编码的视频,这样可以确保它们有同样的视频质量。
接下来将使用x264编码一个视频,使用一个比普通预设稍慢的预设,这样可以得到比默认设置稍好一点的视频质量。
ffmpeg -i input -c:v libx264 -preset slow -crf 22-c:a copy output.mkv
注意在这个例子中,输入文件的音频流被简单地拷贝到输出,并没有重编码。
两遍模式:
如果你的目标是一个确定大小的文件而且帧与帧之间的视频质量并不重要,这个方法很适用。这通过一个例子可以得到很好地解释。你的视频有10分钟(600秒)的时长同时要求输出为50MB,因为比特率=文件大小/时长,
50MB*8192(MB转kilobits)/600秒=683 kbps(全局比特率)
,683kbps-128kbps(音频比特率)=555kbps(视频比特率),
两边编码的例子:
ffmpeg -y -i input -c:v libx264 -preset medium -b:v 555k -pass 1 -an -f mp4 /dev/null &&
ffmpeg -i input -c:v libx264 -preset medium -b:v 555k -pass 2 -c:a libfdkaac -b:a 128k mp4 output.mp4
注意 windows 用户应该使用NUL来取代/dev/null
当使用CRF时,请选择使用你所能容忍的最慢预设。
同时建议你看一下《制作高质量的 MPEG4 DVD电影视频剪辑》,这是一篇 MPEG4 编码器编码指南,它会让你深刻的了解当你面临存储空间受限时,两边编码模式对于有效的使用每一个bit是多么的重要。
你可以使用-qp 0或者-crf 0 来编码一个无损输出,对于无损压缩我们提倡使用-qp 胜过-crf。因为8 bitx264和10 bitx264中的 crf 针对无损模式使用了不同的值。对此ultrafast和veryslow是两个非常有用的预设,因为飞快的编码速度和出色的压缩比通常是两个非常重要的因素。大部分的非 ffmpeg播放器不能播放无损模式,所以如果考虑到兼容性问题,你可能不能使用无损模式。
无损压缩的例子(快速编码)
ffmpeg -i input -c:v libx264 -preset ultrafast -qp 0 output.mkv
无损压缩的例子(高压缩比)
ffmpeg -i input -c:v libx264 -preset veryslow -qp 0 output.mkv
重写缺省预设
你可以使用-x264opts来重写预设或者使用 libx264的私有选项(可以通过ffmpeg -h来完整的查看 libx264选项)。我们并不建议你这么做除非你知道你在做什么。所有预设均是由x264的开发者创建的,想通过微调参数来提高输出质量通常是在浪费时间。
ffmpeg -i input -c:v libx264 -preset slow -crf 22 -x264opts
keyint=123:min-keyint=20 -c:a copy output.mkv
附加信息:
ABR(Average Bit Rate)
ffmpeg -i input -c:v libx264 -b:v 1000k ....
它提供了某种&运行均值&的目标,终极目标是最终文件大小匹配这个&全局平均&数字(因此基本上来说,如果编码器遇到大量码率开销非常小的黑帧,它将以低于要求的比特率编码,但是在接下来几秒内的非黑帧它将以高质量方式编码方式使码率回归均值)使用两边编码模式是这个方法变得更加有效,你可以和&max bit rate &配合使用来防止码率的波动。
CBR(Constant Bit Rate)
事实上根本就没有CBR这种模式,但是你可以通过补充ABR参数&模拟&一个恒定比特率设置,比如:
ffmpeg -i input -c:v libx264 -b:v 4000k -minrate 4000k -maxrate 4000k -bufsize 1835k out.m2v
在这个例子中,-bufsize是一个&码率控制缓冲区&,因此它会在每一个有用的1835k视频数据内强制一个你所要求的均值(此处为4000k),所以基本上我们会认为接收端/终端播放器会缓冲那么多的数据,因此在这个数据内部波动是没有问题的。
当然,如果只有黑帧或者空白帧,它所花费的的比特率将少于需求位率(但它会尽可能的提高质量水平,直到crf)。
最大比特率的CRF模式
你可以通过声明-crf和-maxrate设置来使用带有最大比特率crf模式,比如:
ffmpeg -i input -c:v libx264 -crf 20 -maxrate 400k
-bufsize 1835k
这将会有效的将crf值锁定在20,但是如果输出码率超过400kbps,在这种情况下编码器会将质量降低到低于crf 20。
x264提高了一个 -tune zerolatency 选项。
如果你想让你的视频最大化的和目标播放设备兼容(比如老版本的的ios或者所有的android 设备),那么你可以这做:
-profile:v baseline
这将会关闭很多高级特性,但是它会提供很好的兼容性。也许你可能不需要这些设置,因为一旦你用了这些设置,在同样的视频质量下与更高的编码档次相比会使比特率稍有增加。
关于profile列表和关于它们的描述,你可以运行x264 --fullhelp
要牢记apple quick time 对于x264编码的视频只支持 YUV 420颜色空间,而且不支持任何高于 mian profile编码档次。这样对于quick time 只留下了两个兼容选项baseline和 main。其他的编码档次qucik time均不支持,虽然它们均可以在其它的播放设备上回放。
使用-ss和-t选项可以编码一个段落而不是整个视频,这样可以快速的了解视频编码输出情况。
-ss 从起始值算起的偏移时间,这个值可以以秒为单位或者HH:MM:SS格式
输出时延,这个值可以以秒为单位或者HH:MM:SS格式&
问题与解答:
1 两遍编码模式能够比CRF模式提供更好的质量吗?
不能,但它可以更加精确地控制目标文件大小。
2 为什么 placebo 是一个浪费时间的玩意儿?
与 veryslow相比,它以极高的编码时间为代价换取了大概1%的视频质量提升,这是一种收益递减准则,veryslow 与 slower相比提升了3%;slower 与 slow相比提升了5%;slow 与 medium相比提升了5%~10%。
3 为什么我的无损输出看起来是无损的?
这是由于rgb-&yuv的转换,如果你转换到yuv444,它依然是无损的。
4 显卡能够加速x264的编码吗?
不,x264没有使用(至少现在没有),有一些私有编码器使用了GPU加快了编码速度,但这并不意味着它们经过良好的优化。也有可能还不如x264,或许速度更慢。总的来说,ffmpeg到目前为止还不支持GPU。
翻译注释:x264在2013版中已经开始支持基于opencl的显卡加速,用于帧类型的判定。
5 为Quick time 播放器压制视频
你需要使用-pix_fmt yuv420p来是你的输出支持QT 播放器。这是因为对于H.264视频剪辑苹果的Quick time只支持 YUV420颜色空间。否则ffmpeg会根据你的视频源输出与Quick time 不兼容的视频格式或者不是基于ffmpeg的视频。
阅读(...) 评论()如何强制ffmpeg编码时输出一个关键帧
如何强制ffmpeg编码时输出一个关键帧
如何强制ffmpeg编码时输出一个关键帧
AVCodecContext *c
//编码器环境句柄
AVFrame* f
//需要编码的一帧视频
/*在avcodec.h文件中有这样的定义
#define FF_I_TYPE &1 ///& Intra
#define FF_P_TYPE &2 ///& Predicted
#define FF_B_TYPE &3 ///& Bi-dir predicted
#define FF_S_TYPE &4 ///& S(GMC)-VOP MPEG4
#define FF_SI_TYPE 5 ///& Switching Intra
#define FF_SP_TYPE 6 ///& Switching Predicted
#define FF_BI_TYPE 7
在编码前设置
f-&pict_type=FF_I_TYPE;&
f-&key_frame=1;&
注:该帧为I帧时,f-&pict_type==FF_I_TYPE &&& f-&key_frame==1
*outsize = avcodec_encode_video(c, temp, outbuf_size, f);
则编码之后通过如下参数判断是否为关键帧:
key_frame=c-&coded_frame-&key_&
pict_type=c-&coded_frame-&pict_
key_frame==FF_I_TYPE && pict_type==1
ffmpeg如何提取视频的关键帧
ffmpeg SDK就支持,以下代码是ffmpeg官方小组提供的
live555+ffmpeg如何提取关键帧(I帧,P帧,B帧)开发流媒体播放器的时候,特别是在windows &mobile,symbian(S60)平台开发时,很可能遇到需要自己开发播放器的情况。S60平台提供了CVideoPlayUtility接口可以实现流媒体播放器,但由于非开源,所以相对于自己开发播放器,很多操作受到限制。live555主要用于网络流接收,ffmpeg则是对接收到的数据进行编码/解码。I帧,P帧,B帧是视频流中三种分类,其中I帧也就是关键帧是基础帧,P帧一般根据I帧确定,而B帧需要前面两着的信息。举例说:the Input sequence for video encoder1 &2 & 3 & 4 & 5 & &6 & 7I & &B & B & P &B & B & I&Let's take 1,2,3.. as PTS for simplification&the out sequence for video encoder ( this equals the decoder sequence)1 &4 & 2 & &3 & 7 & 5 & 6I & P & &B & B & I & &B & B&播放器LIVE555收到的序列顺序就应该是:&1 &4 &2 &3 &7 &5 &6&&经过解码器解码,顺序又回到1 2 3 4 5 6 7这种正常顺序。&所以我们可以根据avcodec_decode_video来判断帧别。avcodec_decode_video之后的顺序是一定的。严格按照1 2 3 4。。。这样的顺序来。判断I帧,P,B帧方法:(1):假如解码成功,则不是I帧就是P帧(根据AVFrame-&keyframe判断是否是I帧)。假如不是I帧,也不是P帧,则只能是B帧(通过pts判断)。(2):采用AVFrame-&pict_type综合pts的办法:if(FF_I_TYPE==picture-&pict_type)&& & & & & & & & & & & &{&& & & & & & & & & & & & & & & &Printlog(&&II&&);&& & & & & & & & & & & &}&& & & & & & & & & & & &else if(FF_P_TYPE==picture-&pict_type)&& & & & & & & & & & & &{&& & & & & & & & & & & & & & & &Printlog(&&PP&&);&& & & & & & & & & & & &}&& & & & & & & & & & & &else if(FF_B_TYPE==picture-&pict_type)&& & & & & & & & & & & &{&& & & & & & & & & & & & & & & &Printlog(&&BB&&);&& & & & & & & & & & & &}&& & & & & & & & & & & &else if(FF_S_TYPE==picture-&pict_type)&& & & & & & & & & & & &{&& & & & & & & & & & & & & & & &Printlog(&&SS&&);&& & & & & & & & & & & &}&& & & & & & & & & & & &else&& & & & & & & & & & & &{&& & & & & & & & & & & & & & & &Printlog(&&OtherType&&);&& & & & & & & & & & & &}正常情况下是不会打印出B帧的,因为解码成功的肯定是I帧或者是P帧.
我的热门文章
即使是一小步也想与你分享ffmpeg编码 - HenryWen - 博客园
邮箱: QQ:
1. 注册所有容器格式和CODEC:av_register_all()2. 打开文件:av_open_input_file()3. 从文件中提取流信息:av_find_stream_info()4. 穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO5. 查找对应的解码器:avcodec_find_decoder()6. 打开编解码器:avcodec_open()7. 为解码帧分配内存:avcodec_alloc_frame()8. 不停地从码流中提取出帧数据:av_read_frame()9. 判断帧的类型,对于视频帧调用:avcodec_decode_video()10. 解码完后,释放解码器:avcodec_close()11. 关闭输入文件:av_close_input_file()
FfmpegEncoder.h
* FfmpegEncoder.h
* Current, Can Support YUV422sp encoder and decoder
Created on: Dec 5, 2010
Author: Henry.Wen
#ifndef _H264ENCODER_H
#define _H264ENCODER_H
void save_image(const char* filePath, const void* bufferBase, int width, int height);
int encoder_init(const char* filePath, int width, int height);
int encoder_frame(const void* frame);
int encoder_frame_yuv422(const void* frame);
void encoder_close();
 FfmpegEncoder.cpp
#include &math.h&
#include &stdio.h&
#include &stdlib.h&
#include &string.h&
extern "C"
#include &libavutil/mathematics.h&
#include &libavformat/avformat.h&
//#include &libswscale/swscale.h&
#include &skia/core/SkBitmap.h&
#include &skia/images/SkImageEncoder.h&
#include &android_runtime/AndroidRuntime.h&
#include "FfmpegEncoder.h"
AVOutputFormat
AVFormatContext
*g_video_codec
*g_video_st
g_frame_count
g_video_pts
g_flagInit
static Mutex sg_mutexL
#ifndef LOGI
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO,
"H264ENCODE", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "H264ENCODE", __VA_ARGS__))
void save_image(const char* filePath, const void* bufferBase, int width, int height)
Mutex::Autolock lock(sg_mutexLock);
#if (ANDROID_r4_4_0)
b.setConfig(SkBitmap::kARGB_8888_Config, width, height,(size_t)0);
b.setConfig(SkBitmap::kARGB_8888_Config, width, height);
b.setPixels((void*)bufferBase);
SkImageEncoder::EncodeFile(filePath, b, SkImageEncoder::kJPEG_Type, SkImageEncoder::kDefaultQuality);
LOGI("save_image image ok====================");
AVStream *add_stream(AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id, int width, int height)
AVCodecContext *c;
AVStream *
/* find the encoder */
*codec = avcodec_find_encoder(codec_id);
LOGI("encoder_init add_stream find encoder='%s'", avcodec_get_name(codec_id));
if (!(*codec))
LOGE("encoder_init add_stream could not find encoder for '%s'\n", avcodec_get_name(codec_id));
st = avformat_new_stream(oc, *codec);
LOGE("encoder_init add_stream could not allocate stream");
st-&id = oc-&nb_streams-1;
avcodec_get_context_defaults3(c, *codec);
c-&codec_id
= codec_//AV_CODEC_ID_MPEG4;
c-&bit_rate
/* Resolution must be a multiple of two. */
c-&time_base.den = 25;
c-&time_base.num = 1;
c-&gop_size
= 12; /* emit one intra frame every twelve frames at most */
c-&pix_fmt
= AV_PIX_FMT_YUV420P;
if (c-&codec_id == AV_CODEC_ID_MPEG2VIDEO)
c-&max_b_frames = 2;
if (c-&codec_id == AV_CODEC_ID_MPEG1VIDEO)
c-&mb_decision = 2;
/* Some formats want stream headers to be separate. */
if (oc-&oformat-&flags & AVFMT_GLOBALHEADER)
c-&flags |= CODEC_FLAG_GLOBAL_HEADER;
if(!strcmp(oc-&oformat-&name, "mp4") || !strcmp(oc-&oformat-&name, "mov") || !strcmp(oc-&oformat-&name, "3gp"))
c-&flags |= CODEC_FLAG_GLOBAL_HEADER;
AVFrame* alloc_picture(AVPixelFormat pix_fmt, int width, int height)
uint8_t *picture_
picture = avcodec_alloc_frame();
if (!picture)
return NULL;
size = avpicture_get_size(pix_fmt, width, height);
picture_buf = (uint8_t *)av_malloc(size);
if (!picture_buf)
av_free(picture);
return NULL;
avpicture_fill((AVPicture *)picture, picture_buf, pix_fmt, width, height);
int open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st)
int ret = 0;
AVCodecContext *c = st-&
/* open the codec */
ret = avcodec_open2(c, codec, NULL);
if (ret & 0)
LOGE("encoder_init open_video could not open video codec: %s", av_err2str(ret));
return -1;
g_frame = avcodec_alloc_frame();
if (!g_frame)
LOGE("encoder_init open_video could not allocate video frame");
return -1;
ret = avpicture_alloc(&g_picture, c-&pix_fmt, c-&width, c-&height);
if (ret & 0)
LOGE("encoder_init open_video could not allocate picture: %s", av_err2str(ret));
free(g_frame);
g_frame = 0;
return -1;
*((AVPicture *)g_frame) = g_
void close_video(AVStream *st)
if(st-&codec)
avcodec_close(st-&codec);
st-&codec = 0;
if(g_frame)
av_free(g_picture.data[0]);
av_free(g_frame);
g_frame = 0;
int encoder_init(const char* filePath, int width, int height)
if(g_flagInit)
Mutex::Autolock lock(sg_mutexLock);
int ret = 0;
LOGI("encoder_init ============begin");
if(!filePath || width &= 0 || height &= 0)
LOGE("encoder_init input parameters error ret = %d", (ret = -1));
return -1;
av_register_all();
/* allocate the output media context */
avformat_alloc_output_context2(&g_oc, NULL, NULL, filePath);
if (!g_oc)
LOGI("Could not deduce output format from file extension: using MPEG.");
return -1;
g_fmt = g_oc-&
g_video_st = add_stream(g_oc, &g_video_codec, g_fmt-&video_codec, width, height);
av_dump_format(g_oc, 0, filePath, 1);
if (g_video_st)
if(open_video(g_oc, g_video_codec, g_video_st) & 0)
LOGE("encoder_init open_video fail!");
close_video(g_video_st);
av_free(g_oc);
return -1;
LOGE("encoder_init g_video_st is null, not enough memory!");
av_free(g_oc);
return -1;
/* open the output file, if needed */
if (!(g_fmt-&flags & AVFMT_NOFILE))
LOGI("encoder_init avio_open ============begin");
ret = avio_open(&g_oc-&pb, filePath, AVIO_FLAG_WRITE);
LOGI("encoder_init avio_open ret:%d============end", ret);
if (ret & 0)
LOGE("encoder_init could not open '%s': %s", filePath, av_err2str(ret));
close_video(g_video_st);
av_free(g_oc);
return -1;
LOGI("encoder_init avformat_write_header video file");
ret = avformat_write_header(g_oc, NULL);
if (ret & 0)
LOGE("encoder_init error occurred when opening output file: %s\n", av_err2str(ret));
close_video(g_video_st);
av_free(g_oc);
return -1;
if (g_frame)
g_frame-&pts = 0;
g_flagInit
LOGI("encoder_init ============end");
static struct SwsContext *swsC
void fill_yuv_image(AVFrame *pict, const void* frame)
int x, y, tmpIndex = 0, tmpWdith = g_width && 1, tmpHeight = g_height && 1;
unsigned char* tmpBuffer_yuv = (unsigned char*)
for (y = 0; y & g_ ++y)
for (x = 0; x & g_ ++x)
pict-&data[0][y * pict-&linesize[0] + x] = *(tmpBuffer_yuv + tmpIndex);
tmpIndex = 0;
int tmpLength = g_width * g_
unsigned char* tmpBuffer_uv = tmpBuffer_yuv + tmpL
/* Cb and Cr */
for (y = 0; y & tmpH ++y)
for (x = 0; x & tmpW ++x)
pict-&data[1][y * pict-&linesize[1] + x] = *(tmpBuffer_uv + tmpIndex + 1);
pict-&data[2][y * pict-&linesize[2] + x] = *(tmpBuffer_uv + tmpIndex);
tmpIndex+= 2;
//fill AVFrame with YUV422p buffer
void fill_yuv422p_image(AVFrame *pict, const void* frameYUV422p)
int width = g_width, height = g_
unsigned char * pyuv422
= (unsigned char *)frameYUV422p;
unsigned char * pyuv420y
= &pict-&data[0][0];
unsigned char * pyuv420u = &pict-&data[1][0];
unsigned char * pyuv420v = &pict-&data[2][0];
int uv_count = 0;
for (i = 0; i & i += 2)
for (j = 0; j & j += 2) {
memcpy(pyuv420y + i * width + j, pyuv422 + i * width * 2 + j * 2,
memcpy(pyuv420y + (i + 1) * width + j,
pyuv422 + (i + 1) * width * 2 + j * 2, 1);
memcpy(pyuv420y + i * width + (j + 1),
pyuv422 + i * width * 2 + (j + 1) * 2, 1);
memcpy(pyuv420y + (i + 1) * width + (j + 1),
pyuv422 + (i + 1) * width * 2 + (j + 1) * 2, 1);
//&&&A&U &&O&A&
memcpy(pyuv420u + uv_count, pyuv422 + i * width * 2 + j * 2 + 1, 1);
//&&&A&V&&O&A&;
memcpy(pyuv420v + uv_count,
pyuv422 + (i + 1) * width * 2 + (j + 1) * 2 + 1, 1);
uv_count++;
/*int x, y, tmpIndex = 0, tmpWdith = g_width && 1, tmpHeight = g_height && 1;
unsigned char* tmpBuffer_yuv = (unsigned char*)frameYUV422p;
/*for (y = 0; y & g_ ++y)
for (x = 0; x & g_ ++x)
pict-&data[0][y * pict-&linesize[0] + x] = *(tmpBuffer_yuv + tmpIndex);
tmpIndex = 0;
int tmpLength = g_width * g_
unsigned char* tmpBuffer_uv = tmpBuffer_yuv + tmpL
// Cb and Cr
for (y = 0; y & tmpH ++y)
for (x = 0; x & tmpW ++x)
pict-&data[1][y * pict-&linesize[1] + x] = *(tmpBuffer_uv + tmpIndex);
pict-&data[2][y * pict-&linesize[2] + x] = *(tmpBuffer_uv + tmpIndex +1);
tmpIndex += 2;
tmpIndex += g_
void write_video_frame(AVFormatContext *oc, AVStream *st)
static struct SwsContext *sws_
AVCodecContext *c = st-&
/* encode the image */
av_init_packet(&pkt);
pkt.data = NULL;
// packet data will be allocated by the encoder
pkt.size = 0;
ret = avcodec_encode_video2(c, &pkt, g_frame, &got_output);
if (ret & 0)
LOGE("encoder_init error encoding video frame: %s\n", av_err2str(ret));
//If size is zero, it means the image was buffered.
if (got_output)
if (c-&coded_frame-&key_frame)
pkt.flags |= AV_PKT_FLAG_KEY;
pkt.stream_index = st-&
ret = av_interleaved_write_frame(oc, &pkt);
av_free_packet(&pkt);
if (ret != 0)
LOGE("encoder_init error while writing video frame: %s\n", av_err2str(ret));
++g_frame_count = 0;
int encoder_frame(const void* frame)
if(!g_flagInit)
Mutex::Autolock lock(sg_mutexLock);
fill_yuv_image(g_frame, frame);
if (g_video_st)
g_video_pts = (double)g_video_st-&pts.val * g_video_st-&time_base.num / g_video_st-&time_base.
g_video_pts = 0.0;
write_video_frame(g_oc, g_video_st);
g_frame-&pts += av_rescale_q(1, g_video_st-&codec-&time_base, g_video_st-&time_base);
int encoder_frame_yuv422(const void* frame)
if(!g_flagInit)
Mutex::Autolock lock(sg_mutexLock);
fill_yuv422p_image(g_frame, frame);
if (g_video_st)
g_video_pts = (double)g_video_st-&pts.val * g_video_st-&time_base.num / g_video_st-&time_base.
g_video_pts = 0.0;
write_video_frame(g_oc, g_video_st);
g_frame-&pts += av_rescale_q(1, g_video_st-&codec-&time_base, g_video_st-&time_base);
void encoder_close()
LOGI("encoder_close ============begin");
Mutex::Autolock lock(sg_mutexLock);
av_write_trailer(g_oc);
if (g_video_st)
close_video(g_video_st);
for(int i = 0; i & (int)g_oc-&nb_ ++i)
av_freep(&g_oc-&streams[i]-&codec);
av_freep(&g_oc-&streams[i]);
if (!(g_fmt-&flags & AVFMT_NOFILE))
avio_close(g_oc-&pb);
av_free(g_oc);
g_video_st
g_flagInit
g_frame_count
LOGI("encoder_close ============end");
  首先第一件事情就是开一个视频文件并从中得到流。我们要做的第一件事情就是使用av_register_all();来初始化libavformat/libavcodec:&
这一步注册库中含有的所有可用的文件格式和编码器,这样当打开一个文件时,它们才能够自动选择相应的文件格式和编码器。av_register_all()只需调用一次,所以,要放在初始化代码中。也可以仅仅注册个人的文件格式和编码。  下一步,打开文件:AVFormatContext *pFormatCconst char
*filename="myvideo.mpg";av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL);
// 打开视频文件最后三个参数描述了文件格式,缓冲区大小(size)和格式参数;我们通过简单地指明NULL或0告诉 libavformat 去自动探测文件格式并且使用默认的缓冲区大小。这里的格式参数指的是视频输出参数,比如宽高的坐标。  下一步,我们需要取出包含在文件中的流信息:av_find_stream_info(pFormatCtx);
// 取出流信息AVFormatContext 结构体dump_format(pFormatCtx, 0, filename, false);//我们可以使用这个函数把获取到得参数全部输出。for(i=0; i&pFormatCtx-&nb_ i++)
//区分视频流和音频流 if(pFormatCtx-&streams-&codec.codec_type==CODEC_TYPE_VIDEO) //找到视频流,这里也可以换成音频
videoStream=i;
}接下来就需要寻找解码器AVCodec *pCpCodec=avcodec_find_decoder(pCodecCtx-&codec_id);avcodec_open(pCodecCtx, pCodec);
// 打开解码器给视频帧分配空间以便存储解码后的图片:AVFrame *pFpFrame=avcodec_alloc_frame();/////////////////////////////////////////开始解码///////////////////////////////////////////第一步当然是读数据:我们将要做的是通过读取包来读取整个视频流,然后把它解码成帧,最后转换格式并且保存。while(av_read_frame(pFormatCtx, &packet)&=0) {
if(packet.stream_index==videoStream){
//判断是否视频流
avcodec_decode_video(pCodecCtx,pFrame, &frameFinished,packet.data, packet.size);
if(frameFinished) {img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx-&pix_fmt, pCodecCtx-&width,pCodecCtx-&height);//转换 SaveFrame(pFrameRGB, pCodecCtx-&width,pCodecCtx-&height, i); //保存数据av_free_packet(&packet);
//释放av_read_frame()读取一个包并且把它保存到AVPacket结构体中。这些数据可以在后面通过av_free_packet()来释 放。函数avcodec_decode_video()把包转换为帧。然而当解码一个包的时候,我们可能没有得到我们需要的关于帧的信息。因此,当我们得 到下一帧的时候,avcodec_decode_video()为我们设置了帧结束标志frameFinished。最后,我们使用 img_convert()函数来把帧从原始格式(pCodecCtx-&pix_fmt)转换成为RGB格式。要记住,你可以把一个 AVFrame结构体的指针转换为AVPicture结构体的指针。最后,我们把帧和高度宽度信息传递给我们的SaveFrame函数。到此解码完毕,显示过程使用SDL完成考虑到我们以后会使用firmware进行显示操作,SDL忽略不讲。音视频同步DTS(解码时间戳)和PTS(显示时间戳)当我们调用av_read_frame()得到一个包的时候,PTS和DTS的信息也会保存在包中。但是我们真正想要的PTS是我们刚刚解码出来的 原始帧 的PTS,这样我们才能知道什么时候来显示它。然而,我们从avcodec_decode_video()函数中得到的帧只是一个AVFrame,其中并 没有包含有用的PTS值(注意:AVFrame并没有包含时间戳信息,但当我们等到帧的时候并不是我们想要的样子)。。我们保存一帧的第一个包的PTS: 这将作为整个这一帧的PTS。我们 可以通过函数avcodec_decode_video()来计算出哪个包是一帧的第一个包。怎样实现呢?任何时候当一个包开始一帧的时 候,avcodec_decode_video()将调用一个函数来为一帧申请一个缓冲。当然,ffmpeg允许我们重新定义那个分配内存的函数。计算前 一帧和现在这一帧的时间戳来预测出下一个时间戳的时间。同时,我们需要同步视频到音频。我们将设置一个音频时间audioclock;一个内部值记录了我 们正在播放的音频的位置。就像从任意的mp3播放器中读出来的数字一样。既然我们把视频同步到音频,视频线程使用这个值来算出是否太快还是太慢。
随笔 - 105}

我要回帖

更多关于 ffmpeg编解码流程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信