ffmpeg学习笔记
- 格式:doc
- 大小:125.50 KB
- 文档页数:13
FFmpeg从⼊门到精通:SEI那些事本⽂是“FFmpeg从⼊门到精通”系列的第三篇,由⾦⼭云供稿,并授权LiveVideoStack发布。
此前两篇为和。
FFmpeg⼴泛应⽤与⾳视频领域,被誉为⾳视频开发的“瑞⼠军⼑”。
“FFmpeg从⼊门到精通”系列将由浅⼊深,解读FFmpeg的基础功能与使⽤技巧。
流媒体是采⽤流式传输⽅式在⽹络上播放的媒体格式,视频⽹站内容、短视频、在线直播这些视频形态,均属于流媒体的不同分⽀。
流媒体⼤致包含三个层级:码流、封装和协议。
从⾳视频编码器输出的码流,经过某种封装格式后,经过特定的协议传输、保存,构成了流媒体世界的基础功能。
在直播应⽤的开发过程中,如果把主播端消息事件传递到观众端,⼀般会以Instant Messaging(即时通讯)的⽅式传递过去,但因为消息分发通道和直播通道是分开的,因此消息与直播⾳视频数据的同步性就会出现很多问题。
那么有没有在⾳视频内部传递消息的⽅法呢?答案是SEI。
⾦⼭云⽬前推出的直播问答解决⽅案中,就⽤到了SEI,作为⼀名视频云架构资深开发⼯程师,今天就来和⼤家分享⼀下SEI的技术细节。
SEI即补充增强信息(Supplemental Enhancement Information),属于码流范畴,它提供了向视频码流中加⼊额外信息的⽅法,是H.264/H.265这些视频压缩标准的特性之⼀。
SEI的基本特征如下:1. 并⾮解码过程的必须选项2. 可能对解码过程(容错、纠错)有帮助3. 集成在视频码流中也就是说,视频编码器在输出视频码流的时候,可以不提供SEI信息。
虽然在视频的传输过程、解封装、解码这些环节,都可能因为某种原因丢弃SEI内容,但在视频内容的⽣成端和传输过程中,是可以插⼊SEI信息的。
这些插⼊的信息,和其他视频内容⼀同经过传输链路到达消费端。
举例来说,当前⽕爆的直播问答模式,就是通过SEI传递较多和答题业务相关的信息,通过SEI承载的信息,极⼤地优化了题⽬显⽰和观众⾳视频观看的同步性。
ffmpeg 常用命令汇总2016-06-15 09:11 17116人阅读评论(0)收藏举报(经常用到ffmpeg 做一些视频数据的处理转换等,用来做测试,今天总结了一下,参考了网上部分朋友的经验,一起在这里汇总了一下,有需要的朋友可以收藏测试一下,有问题欢迎在下面回帖交流,谢谢;by ternence.hsu)1、ffmpeg使用语法命令格式:ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出文件]ffmpeg [[options][`-i' input_file]]... {[options] output_file}...1、参数选项:(1) -an: 去掉音频(2) -acodec: 音频选项,一般后面加copy表示拷贝(3) -vcodec:视频选项,一般后面加copy表示拷贝2、格式:(1) h264: 表示输出的是h264的视频裸流(2) mp4: 表示输出的是mp4的视频(3)mpegts: 表示ts视频流如果没有输入文件,那么视音频捕捉(只在Linux下有效,因为Linux下把音视频设备当作文件句柄则,选项一般用于下一个特定的文件。
如果你给–b 64选项,改选会设置下一个视频速率。
对于原始情况下,ffmpeg试图尽可能的无损转换,采用与输入同样的音频视频参数来输出。
(by ternence.hsu)2、视频转换H264视频转ts视频流ffmpeg -i test.h264 -vcodec copy -f mpegts test.tsH264视频转mp4ffmpeg -i test.h264 -vcodec copy -f mp4 test.mp4ts视频转mp4ffmpeg -i test.ts -acodec copy -vcodec copy -f mp4 test.mp4mp4视频转flvffmpeg -i test.mp4 -acodec copy -vcodec copy -f flv test.flv转换文件为3GP格式ffmpeg -y -i test.mpeg -bitexact -vcodec h263 -b 128 -r 15 -s 176x144 -acodec aac -ac 2 -ar 22500 -ab 24 -f 3转换文件为3GP格式v2ffmpeg -y -i test.wmv -ac 1 -acodec libamr_nb -ar 8000 -ab 12200 -s 176x144 -b 128 -r 15 test.3gp使用ffmpeg 编码得到高质量的视频ffmpeg.exe -i "D:\Video\Fearless\Fearless.avi" -target film-dvd -s 720x352 -padtop 64 -padbottom 64 -maxra 1000000000 -trellis -cgop -g 12 -bf 2 -qblur 0.3 -qcomp 0.7 -me full -dc 10 -mbd 2 -aspect 16:9 -pass 2 -pass mpeg2video "D:\Fearless.m2v"转换指定格式文件到FLV格式ffmpeg.exe -i test.mp3 -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\test.flvffmpeg.exe -i test.wmv -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\test.flv转码解密的VOBffmpeg -i snatch_1.vob -f avi -vcodec mpeg4 -b 800 -g 300 -bf 2 -acodec mp3 -ab 128 snatch.avi(上面的命令行将vob的文件转化成avi文件,mpeg4的视频和mp3的音频。
ffmpeg 知识点汇总一、基本概念•FFmpeg:一个自由且开源的跨平台多媒体框架,包含了丰富的音视频编解码库、协议和滤镜。
•编解码器:将音视频数据进行编码或解码的软件或硬件。
•容器:一种封装音视频数据的格式,如MP4、MOV、AVI等。
•流:一种连续的音视频数据,如一个视频文件就是一个流。
•协议:一种在网络上传输音视频数据的规则,如HTTP、RTMP等。
二、命令行工具•ffmpeg:FFmpeg的核心命令行工具,可以进行各种音视频处理操作。
•ffplay:一个简单的视频播放器,可以播放各种常见的音视频格式。
•ffprobe:一个音视频信息查看工具,可以查看音视频文件的元数据信息。
三、音视频编解码库•H.264:一种常见的视频编解码器,以其较高的压缩率和较好的画质而闻名。
•H.265(HEVC):一种比H.264更新的视频编解码器,具有更高的压缩率和更好的画质。
•AAC:一种常见的音频编解码器,以其较高的音质和较小的体积而闻名。
•MP3:一种常见的音频编解码器,以其较小的体积和较好的音质而闻名。
四、容器格式•MP4:一种常见的容器格式,可以封装H.264、AAC等编解码器的数据。
•MOV:一种常见的容器格式,可以封装H.264、AAC等编解码器的数据。
•AVI:一种常见的容器格式,可以封装各种编解码器的数据。
•MKV:一种常见的容器格式,可以封装各种编解码器的数据。
五、协议•HTTP:一种常用的协议,可以用于在互联网上传输音视频数据。
•RTMP:一种常用的协议,可以用于在互联网上传输实时音视频数据。
•UDP:一种常用的协议,可以用于在互联网上传输实时音视频数据。
六、滤镜•缩放滤镜:可以改变视频的分辨率。
•裁剪滤镜:可以裁剪视频的画面。
•旋转滤镜:可以旋转视频的画面。
•水印滤镜:可以给视频添加水印。
•马赛克滤镜:可以给视频添加马赛克。
七、其他知识点•FFmpeg的安装:可以从FFmpeg官网下载并安装FFmpeg。
ffmpeg编程入门程序ffmpeg是一款开源的音视频处理工具,可以用来对音视频文件进行转码、剪辑、合并等操作。
本文将介绍如何使用ffmpeg编程入门,并提供一些基础的示例代码供参考。
我们需要安装ffmpeg并配置好环境变量。
安装方法可以参考ffmpeg官方网站的文档。
安装完成后,我们可以在命令行窗口中输入ffmpeg命令来验证是否安装成功。
接下来,我们开始编写第一个ffmpeg程序。
我们以将一个视频文件转码为另一种格式为例。
首先,我们需要创建一个新的C++源文件,比如命名为"ffmpeg_demo.cpp"。
然后,我们可以使用以下代码来实现转码功能:```cpp#include <iostream>#include <cstdlib>extern "C" {#include <libavformat/avformat.h>#include <libavcodec/avcodec.h>#include <libavutil/imgutils.h>}int main(int argc, char* argv[]) {// 注册所有的编解码器av_register_all();// 打开输入文件AVFormatContext* inputFormatContext = nullptr;if (avformat_open_input(&inputFormatContext, argv[1], nullptr, nullptr) != 0) {std::cerr << "无法打开输入文件" << std::endl;return -1;}// 查找流信息if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {std::cerr << "无法获取流信息" << std::endl;return -1;}// 打印流信息av_dump_format(inputFormatContext, 0, argv[1], 0);// 查找视频流索引int videoStreamIndex = -1;for (int i = 0; i < inputFormatContext->nb_streams; i++){if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {videoStreamIndex = i;break;}}if (videoStreamIndex == -1) {std::cerr << "找不到视频流" << std::endl;return -1;}// 获取视频解码器参数AVCodecParameters* videoCodecParameters = inputFormatContext->streams[videoStreamIndex]->codecpar;// 查找视频解码器AVCodec* videoCodec = avcodec_find_decoder(videoCodecParameters->codec_id);if (videoCodec == nullptr) {std::cerr << "找不到视频解码器" << std::endl;return -1;}// 创建视频解码器上下文AVCodecContext* videoCodecContext = avcodec_alloc_context3(videoCodec);if (videoCodecContext == nullptr) {std::cerr << "无法创建视频解码器上下文" << std::endl;return -1;}// 设置视频解码器参数if (avcodec_parameters_to_context(videoCodecContext, videoCodecParameters) < 0) {std::cerr << "无法设置视频解码器参数" << std::endl; return -1;}// 打开视频解码器if (avcodec_open2(videoCodecContext, videoCodec, nullptr) < 0) {std::cerr << "无法打开视频解码器" << std::endl;return -1;}// 创建输出文件AVFormatContext* outputFormatContext = nullptr;if (avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, argv[2]) < 0) {std::cerr << "无法创建输出文件" << std::endl;return -1;}// 添加视频流到输出文件AVStream* videoStream = avformat_new_stream(outputFormatContext, nullptr);if (videoStream == nullptr) {std::cerr << "无法添加视频流到输出文件" << std::endl;return -1;}// 复制视频流参数if (avcodec_parameters_copy(videoStream->codecpar, videoCodecParameters) < 0) {std::cerr << "无法复制视频流参数" << std::endl;return -1;}// 打开输出文件if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {if (avio_open(&outputFormatContext->pb, argv[2], AVIO_FLAG_WRITE) < 0) {std::cerr << "无法打开输出文件" << std::endl;return -1;}}// 写入文件头if (avformat_write_header(outputFormatContext, nullptr) < 0) {std::cerr << "无法写入文件头" << std::endl;return -1;}// 分配AVPacket和AVFrameAVPacket* packet = av_packet_alloc();AVFrame* frame = av_frame_alloc();// 读取帧数据并写入输出文件while (av_read_frame(inputFormatContext, packet) >= 0){if (packet->stream_index == videoStreamIndex) {// 解码视频帧if (avcodec_send_packet(videoCodecContext, packet) < 0) {std::cerr << "无法解码视频帧" << std::endl; break;}while (avcodec_receive_frame(videoCodecContext, frame) >= 0) {// 编码视频帧AVPacket* outputPacket = av_packet_alloc(); if (avcodec_send_frame(videoCodecContext, frame) < 0) {std::cerr << "无法编码视频帧" << std::endl;break;}if(avcodec_receive_packet(videoCodecContext, outputPacket) < 0) {std::cerr << "无法编码视频帧" <<std::endl;break;}outputPacket->stream_index = videoStream->index;// 写入输出文件if(av_interleaved_write_frame(outputFormatContext, outputPacket) < 0) {std::cerr << "无法写入输出文件" << std::endl;break;}av_packet_unref(outputPacket);}av_packet_unref(packet);} else {av_packet_unref(packet);}}// 写入文件尾av_write_trailer(outputFormatContext);// 释放资源av_packet_free(&packet);av_frame_free(&frame);avcodec_close(videoCodecContext);avcodec_free_context(&videoCodecContext);avformat_close_input(&inputFormatContext);avformat_free_context(outputFormatContext);return 0;}```上述代码中,我们首先通过调用`av_register_all()`函数来注册所有的编解码器。
FFMPEG命令入门到提高,一篇文章就够了FFMPEG简介FFmpeg的名称来自MPEG视频编码标准,前面的“FF”代表“Fast Forward”,FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。
可以轻易地实现多种视频格式之间的相互转换。
FFmpeg的用户有Google,Facebook,Youtube,优酷,爱奇艺,土豆等基础知识1.容器/文件(Conainer/File):即特定格式的多媒体文件,比如mp4、flv、mkv等。
2.媒体流(Stream):表示时间轴上的一段连续数据,如一段声音数据、一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。
3.数据帧/数据包(Frame/Packet):通常,一个媒体流是由大量的数据帧组成的,对于压缩数据,帧对应着编解码器的最小处理单元,分属于不同媒体流的数据帧交错存储于容器之中。
一般情况下:Frame对应压缩前的数据,Packet对应压缩后的数据。
4.编解码器(Codec):以帧为单位实现压缩数据和原始数据之间的相互转换的5.复用(mux):把不同的流按照某种容器的规则放入容器,这种行为叫做复用(mux)6.解复用(mux):把不同的流从某种容器中解析出来,这种行为叫做解复用(demux)7.码率和帧率是视频文件的最重要的基本特征,对于他们的特有设置会决定视频质量。
如果我们知道码率和时长那么可以很容易计算出输出文件的大小。
8.帧率:帧率也叫帧频率,帧率是视频文件中每一秒的帧数,肉眼想看到连续移动图像至少需要15帧。
9.码率:比特率(也叫码率,数据率)是一个确定整体视频/音频质量的参数,秒为单位处理的位数,码率和视频质量成正比,在视频文件中中比特率用bps来表达。
FFMPEG命令行环境搭建1.到ffmpeg官方下载已经编译好的Windows shared库;2.将执行文件ffmpeg.exe ffplay.exe ffprobe.exe拷贝到C:\Windows目录;3.将相应的动态库拷贝到C:\Windows\SysWOW64目录;4.注:WOW64 (Windows-on-Windows 64-bit)5.在命令行窗口输入ffmpeg -version 查看版本,以却确定环境是否搭建成功。
视频编解码FFMPEGffmpeg开发指南Posted on 2008-12-08 16:11 李健阅读(672) 评论(0) 编辑收藏引用所属分类: 视频解码笔记ffmpeg开发指南(使用 libavformat 和 libavcodec)Ffmpeg 中的Libavformat 和 libavcodec库是访问大多数视频文件格式的一个很好的方法。
不幸的是,在开发您自己的程序时,这套库基本上没有提供什么实际的文档可以用来作为参考(至少我没有找到任何文档),并且它的例程也并没有太多的帮助。
这种情况意味着,当我在最近某个项目中需要用到 libavformat/libavcodec 库时,需要作很多试验来搞清楚怎样使用它们。
这里是我所学习的,,希望我做的这些能够帮助一些人,以免他们重蹈我的覆辙,作同样的试验,遇到同样的错误。
你还可以从这里下载一个demo程序。
我将要公开的这部分代码需要0.4.8 版本的ffmpeg库中的 libavformat/libavcodec的支持(我正在写最新版本)。
如果您发现以后的版本与我写的程序不能兼容,请告知我。
在这个文档里,我仅仅涉及到如何从文件中读入视频流;音频流使用几乎同样的方法可以工作的很好,不过,我并没有实际使用过它们,所以,我没于办法提供任何示例代码。
或许您会觉得奇怪,为什么需要两个库文件 libavformat 和 libavcodec :许多视频文件格式(AVI就是一个最好的例子)实际上并没有明确指出应该使用哪种编码来解析音频和视频数据;它们只是定义了音频流和视频流(或者,有可能是多个音频视频流)如何被绑定在一个文件里面。
这就是为什么有时候,当你打开了一个AVI文件时,你只能听到声音,却不能看到图象,,因为你的系统没有安装合适的视频解码器。
所以, libavformat 用来处理解析视频文件并将包含在其中的流分离出来,而libavcodec 则处理原始音频和视频流的解码。
ffmpeg超详细综合教程本⽂的⽰例将实现:读取安卓⼿机摄像头数据并使⽤H.264编码格式实时编码保存为flv⽂件。
⽰例包含了1、编译适⽤于安卓平台的ffmpeg库2、在java中通过JNI使⽤ffmpeg3、读取安卓摄像头数据并在后台线程中使⽤ffmpeg进⾏编码的基本流程具有较强的综合性。
编译适⽤于安卓平台的ffmpeg库平时我们编译ffmpeg类库都是在x86平台下,⽽安卓⼿机属于arm平台,所以要先通过交叉编译的⽅法在x86平台下编译出可以在arm平台下使⽤的 ffmpeg类库。
Google就为我们提供了⼀套可以再Linux下完成该任务的r10e,在cygwin下使⽤。
ndk的下载安装基本是傻⽠式的,但要注意linux系统对x86和x64是分的很清楚的,不像windows那么随性,此外还需要提醒的是,在下载安装cygwin时,只需要选择binutils, gcc , gcc-mingw , gdb , make这⼏个组件。
安装完成后,在cygwin安装⽬录下点击cygwin.bat打开命令窗⼝,输⼊make -version验证是否安装成功。
做好以上的准备⼯作之后就可以正式开始ffmpeg源码的编译了。
⾸先需要修改configure⽂件,确保类库版本号不会出现在.so后缀名的后⾯,否则安卓平台⽆法识别这样的类库找到下⾯⼏句[plain]view plaincopy1. SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'2. LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'3. SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'4. SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)'修改为[plain]view plaincopy1. SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'2. LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'3. SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'4. SLIB_INSTALL_LINKS='$(SLIBNAME)'之后进⾏常规的configure配置,make, make install步骤即可,下⾯给出⼀个常规的脚本⽰例[plain]view plaincopy1. make clean2.3. export NDK=xxxx/android-ndk-r10e4. export PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt5. export PLATFORM=$NDK/platforms/android-8/arch-arm6. export PREFIX=../android_ffmpeglib7.8. ./configure --target-os=linux --prefix=$PREFIX \9. --enable-cross-compile \10. --enable-runtime-cpudetect \11. --disable-asm \12. --arch=arm \13. --cc=$PREBUILT/windows/bin/arm-linux-androideabi-gcc \14. --cross-prefix=$PREBUILT/windows/bin/arm-linux-androideabi- \15. --disable-stripping \16. --nm=$PREBUILT/windows/bin/arm-linux-androideabi-nm \17. --sysroot=$PLATFORM \18. --enable-gpl --enable-shared --disable-static --enable-small \19. --disable-ffprobe --disable-ffplay --disable-ffmpeg --disable-ffserver --disable-debug \20. --extra-cflags="-fPIC -DANDROID -D__thumb__ -mthumb -Wfatal-errors -Wno-deprecated -mfloat-abi=softfp -marm -march=armv7-a"21.22. make23. make install成功编译后,可以看到如下类库在java中通过JNI使⽤ffmpegJNI 即java本地接⼝,java native interface,它是⼀个协议, 该协议⽤来沟通Java代码和外部的本地C/C++代码, 通过该协议 Java代码可以调⽤外部的本地代码, 外部的C/C++ 代码可以调⽤Java代码。
ffmpeg 分析学习-2014-12-25in transcode_step() call: //version 2.4.3/** Return* - 0 -- one packet was read and processed* - AVERROR(EAGAIN) -- no packets were available for selected file,* this function should be called again* - AVERROR_EOF -- this function should not be called again*/static int process_input(int file_index){InputFile *ifile = input_files[file_index];AVFormatContext *is;InputStream *ist;AVPacket pkt;int ret, i, j; is = ifile->ctx;ret = get_input_packet(ifile, &pkt); if (ret == AVERROR(EAGAIN)){ifile->eagain = 1;return ret;}if (ret < 0){if (ret != AVERROR_EOF){print_error(is->filename, ret);if (exit_on_error)exit_program(1);} for (i = 0; i < ifile->nb_streams; i++){ist = input_streams[ifile->ist_index + i];if (ist->decoding_needed){ret = process_input_packet(ist, NULL);if (ret>0)return 0;} /* mark all outputs that don't go through lavfi as finished */for (j = 0; j < nb_output_streams; j++){OutputStream *ost = output_streams[j]; if (ost->source_index == ifile->ist_index + i&&(ost->stream_copy ||ost->enc->type == AVMEDIA_TYPE_SUBTITLE))finish_output_stream(ost);}} ifile->eof_reached = 1;return AVERROR(EAGAIN);} //文件已经读到结束reset_eagain(); if (do_pkt_dump){av_pkt_dump_log2(NULL, AV_LOG_DEBUG,&pkt, do_hex_dump,is->streams[pkt.stream_index]);}/* the following test is needed in case new streams appeardynamically in stream : we ignore them */if (pkt.stream_index >= ifile->nb_streams){report_new_stream(file_index, &pkt);goto discard_packet;} ist = input_streams[ifile->ist_index +pkt.stream_index]; ist->data_size += pkt.size;ist->nb_packets++; if (ist->discard)goto discard_packet; if (debug_ts){av_log(NULL, AV_LOG_INFO, "demuxer ->ist_index:%d type:%s ""next_dts:%s next_dts_time:%snext_pts:%s next_pts_time:%s pkt_pts:%spkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s off:%soff_time:%s\n",ifile->ist_index + pkt.stream_index,av_get_media_type_string(ist->dec_ctx->codec_type) ,av_ts2str(ist->next_dts),av_ts2timestr(ist->next_dts, &AV_TIME_BASE_Q),av_ts2str(ist->next_pts),av_ts2timestr(ist->next_pts, &AV_TIME_BASE_Q),av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ist->st->time_base),av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ist->st->time_base),av_ts2str(input_files[ist->file_index]->ts_offset),av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q));} if(!ist->wrap_correction_done &&is->start_time != AV_NOPTS_VALUE &&ist->st->pts_wrap_bits < 64){int64_t stime, stime2;// Correcting starttime based on the enabled streams// FIXME this ideally should be done before the first use of starttime but we do not know which are // the enabled streams at that point.// so we instead do it here as part of discontinuity handlingif ( ist->next_dts == AV_NOPTS_VALUE&& ifile->ts_offset ==-is->start_time&& (is->iformat->flags& AVFMT_TS_DISCONT)){int64_t new_start_time = INT64_MAX;for (i=0; i<is->nb_streams; i++){AVStream *st = is->streams[i];if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE)continue;new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base,AV_TIME_BASE_Q));}if (new_start_time > is->start_time){av_log(is, AV_LOG_VERBOSE, "Correcting start time by %"PRId64"\n", new_start_time -is->start_time);ifile->ts_offset = -new_start_time;}} stime = av_rescale_q(is->start_time, AV_TIME_BASE_Q, ist->st->time_base);stime2= stime +(1ULL<<ist->st->pts_wrap_bits);ist->wrap_correction_done = 1;if(stime2 > stime && pkt.dts !=AV_NOPTS_VALUE && pkt.dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))){pkt.dts -=1ULL<<ist->st->pts_wrap_bits;ist->wrap_correction_done = 0;}if(stime2 > stime && pkt.pts !=AV_NOPTS_VALUE && pkt.pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))){pkt.pts -=1ULL<<ist->st->pts_wrap_bits;ist->wrap_correction_done = 0;}} /* add the stream-global side data to the first packet */if (ist->nb_packets == 1){if (ist->st->nb_side_data)av_packet_split_side_data(&pkt);for (i = 0; i < ist->st->nb_side_data; i++){AVPacketSideData *src_sd =&ist->st->side_data[i];uint8_t *dst_data; if(av_packet_get_side_data(&pkt, src_sd->type, NULL))continue; dst_data =av_packet_new_side_data(&pkt, src_sd->type, src_sd->size);if (!dst_data)exit_program(1);memcpy(dst_data, src_sd->data, src_sd->size);}} if (pkt.dts != AV_NOPTS_VALUE)pkt.dts += av_rescale_q(ifile->ts_offset,AV_TIME_BASE_Q, ist->st->time_base);if (pkt.pts != AV_NOPTS_VALUE)pkt.pts += av_rescale_q(ifile->ts_offset,AV_TIME_BASE_Q, ist->st->time_base); if (pkt.pts != AV_NOPTS_VALUE)pkt.pts *= ist->ts_scale;if (pkt.dts != AV_NOPTS_VALUE)pkt.dts *= ist->ts_scale; if (pkt.dts !=AV_NOPTS_VALUE && ist->next_dts == AV_NOPTS_VALUE && !copy_ts&& (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE){int64_t pkt_dts = av_rescale_q(pkt.dts,ist->st->time_base, AV_TIME_BASE_Q);int64_t delta = pkt_dts - ifile->last_ts;if(delta <-1LL*dts_delta_threshold*AV_TIME_BASE ||(delta >1LL*dts_delta_threshold*AV_TIME_BASE &&ist->dec_ctx->codec_type !=AVMEDIA_TYPE_SUBTITLE)){ifile->ts_offset -= delta;av_log(NULL, AV_LOG_DEBUG,"Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n",delta, ifile->ts_offset);pkt.dts -= av_rescale_q(delta,AV_TIME_BASE_Q, ist->st->time_base);if (pkt.pts != AV_NOPTS_VALUE)pkt.pts -= av_rescale_q(delta,AV_TIME_BASE_Q, ist->st->time_base);}} if (pkt.dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE &&!copy_ts){int64_t pkt_dts = av_rescale_q(pkt.dts,ist->st->time_base, AV_TIME_BASE_Q);int64_t delta = pkt_dts - ist->next_dts;if (is->iformat->flags &AVFMT_TS_DISCONT){if (delta <-1LL*dts_delta_threshold*AV_TIME_BASE ||(delta >1LL*dts_delta_threshold*AV_TIME_BASE &&ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) ||pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)){ifile->ts_offset -= delta;av_log(NULL, AV_LOG_DEBUG,"timestampdiscontinuity %"PRId64", new offset= %"PRId64"\n",delta, ifile->ts_offset);pkt.dts -= av_rescale_q(delta,AV_TIME_BASE_Q, ist->st->time_base);if (pkt.pts != AV_NOPTS_VALUE)pkt.pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base);}} else {if ( delta <-1LL*dts_error_threshold*AV_TIME_BASE ||(delta >1LL*dts_error_threshold*AV_TIME_BASE &&ist->dec_ctx->codec_type !=AVMEDIA_TYPE_SUBTITLE)) {av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invaliddropping\n", pkt.dts, ist->next_dts, pkt.stream_index);pkt.dts = AV_NOPTS_VALUE;}if (pkt.pts != AV_NOPTS_VALUE){int64_t pkt_pts = av_rescale_q(pkt.pts, ist->st->time_base, AV_TIME_BASE_Q);delta = pkt_pts - ist->next_dts;if ( delta <-1LL*dts_error_threshold*AV_TIME_BASE ||(delta >1LL*dts_error_threshold*AV_TIME_BASE &&ist->dec_ctx->codec_type !=AVMEDIA_TYPE_SUBTITLE)) {av_log(NULL, AV_LOG_WARNING,"PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt.pts, ist->next_dts, pkt.stream_index);pkt.pts = AV_NOPTS_VALUE;}}}} if (pkt.dts != AV_NOPTS_VALUE)ifile->last_ts = av_rescale_q(pkt.dts,ist->st->time_base, AV_TIME_BASE_Q); if (debug_ts) {av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s off:%s off_time:%s\n",ifile->ist_index + pkt.stream_index,av_get_media_type_string(ist->dec_ctx->codec_type) ,av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ist->st->time_base),av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ist->st->time_base),av_ts2str(input_files[ist->file_index]->ts_offset),av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q));} sub2video_heartbeat(ist, pkt.pts); ret = process_input_packet(ist, &pkt);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Error while decoding stream #%d:%d: %s\n",ist->file_index, ist->st->index, av_err2str(ret));if (exit_on_error)exit_program(1);}discard_packet:av_free_packet(&pkt); return 0;}int main(int argc, char **argv){int ret;int64_t ti; register_exit(ffmpeg_cleanup); // 注册退出函数setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */av_log_set_flags(AV_LOG_SKIP_REPEATED);parse_loglevel(argc, argv, options); if(argc>1 && !strcmp(argv[1], "-d")){run_as_daemon=1;av_log_set_callback(log_callback_null);argc--;argv++;} avcodec_register_all();#if CONFIG_AVDEVICEavdevice_register_all();#endifavfilter_register_all();av_register_all();avformat_network_init(); show_banner(argc, argv, options); term_init(); /* parse options and open all input/output files */ret = ffmpeg_parse_options(argc, argv);if (ret < 0)exit_program(1); if (nb_output_files <= 0&& nb_input_files == 0) {show_usage();av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n",program_name);exit_program(1);} /* file converter / grab */if (nb_output_files <= 0) {av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");exit_program(1);}// if (nb_input_files == 0) {// av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");// exit_program(1);// } current_time = ti = getutime();if (transcode() < 0)exit_program(1);ti = getutime() - ti;if (do_benchmark) {printf("bench: utime=%0.3fs\n", ti / 1000000.0);}av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",decode_error_stat[0], decode_error_stat[1]);if ((decode_error_stat[0] + decode_error_stat[1]) *max_error_rate < decode_error_stat[1]) exit_program(69);exit_program(received_nb_signals ? 255 : main_return_code);return main_return_code;}。
ffmpeg分析系列hello,各位好,本人是一名嵌入式软件工程师,目前正使用ffmpeg开发一款嵌入式多媒体播放器,《ffmpeg分析》系列博文是本人在阅读ffmpeg源代码时所做的笔记,希望对各位有点帮助。
分析过程结合下面的例程:一. 调用av_register_all函数注册所有的格式和编码解码器.1.1 先调用avcodec_register_all函数注册所有的编码解码器.1. 下面列出与H264相关的注册:2. 下面列出注册宏:#define REGISTER_HWACCEL(X,x){ \extern AVHWAccel x##_hwaccel; \if(CONFIG_##X##_HWACCEL)av_register_hwaccel(&x##_hwaccel);}#define REGISTER_ENCODER(X,x){ \extern AVCodec x##_encoder; \if(CONFIG_##X##_ENCODER)avcodec_register(&x##_encoder);}#define REGISTER_DECODER(X,x){ \extern AVCodec x##_decoder; \if(CONFIG_##X##_DECODER)avcodec_register(&x##_decoder);}#define REGISTER_ENCDEC(X,x)REGISTER_ENCODER(X,x); REGISTER_DECODER(X,x)#define REGISTER_PARSER(X,x){ \extern AVCodecParser x##_parser; \3. 分析一下注册函数, 以avcodec_register函数为例:可以看到avcodec_register函数把输入的AVCodec连成一个链表, 其它注册函数与之类似, 就不多言了.4. 上面调用了avcodec_init函数:这个函数只会真正执行一次.5. 上面调用了dsputil_static_init函数:可以看到, 它初始化了一些静态数据.1.2 注册所有的格式和外部库及协议.1. 下面列出与H264相关的注册:2. 下面列出注册宏:这些注册函数与avcodec_register函数类似, 就不多言了. URL协议结构:libavformat/file.c文件的file协议:staticint file_open(URLContext*h,const char*filename,int flags){int access;int fd;av_strstart(filename,"file:",&filename);if(flags& URL_RDWR){access = O_CREAT | O_TRUNC | O_RDWR;}else if(flags & URL_WRONLY){access = O_CREAT | O_TRUNC | O_WRONLY;}else{access = O_RDONLY;}#ifdef O_BINARYaccess |= O_BINARY;#endiffd =open(filename, access, 0666);if(fd ==-1)return AVERROR(errno);h->priv_data=(void*)(intptr_t) fd;return 0;}static int file_read(URLContext*h,unsigned char*buf,int size) {int fd =(intptr_t) h->priv_data;return read(fd, buf, size);}static int file_write(URLContext*h,unsigned char*buf,int size) {int fd =(intptr_t) h->priv_data;return write(fd, buf, size);}/* XXX: use llseek */static int64_t file_seek(URLContext*h,int64_t pos,int whence) {int fd =(intptr_t) h->priv_data;if(whence== AVSEEK_SIZE){struct stat st;int ret = fstat(fd,&st);return ret < 0 ? AVERROR(errno): st.st_size;}return lseek(fd, pos, whence);}static int file_close(URLContext*h){int fd =(intptr_t) h->priv_data;return close(fd);}static int file_get_handle(URLContext*h){return(intptr_t) h->priv_data;}URLProtocol file_protocol ={"file",file_open,file_read,file_write,file_seek,file_close,.url_get_file_handle = file_get_handle,};libavformat/allformats.c文件的av_register_all函数注册了file 协议:把注册协议函数也贴出来吧:URLProtocol*first_protocol =NULL;int av_register_protocol(URLProtocol*protocol) {URLProtocol **p;p =&first_protocol;while(*p!=NULL) p =&(*p)->next;*p = protocol;protocol->next=NULL;return 0;}/u3/104564/showart_2369209.html探测数据结构:h264的探测函数:2, 2, 2, 0, 2, 2, 2, 2,2, 2, 2, 2, 2, 2, 2, 2 };if(code& 0x80)//forbidden bitreturn 0;if(ref_zero[type]== 1 && ref_idc) return 0;if(ref_zero[type]==-1&&!ref_idc) return 0;if(ref_zero[type]== 2)res ;switch(type){case 1: sli ;break;case 5: idr ;break;case 7:if(p->buf[i2]&0x0F)return 0;sps ;break;case 8: pps ;break;}视频读首部函数:/* for MPEG-4 specify it, too (most MPEG-4 streams do not have the fixed_vop_rate set ...)*/if(ap->time_base.num){st->codec->time_base= ap->time_base;}else if( st->codec->codec_id== CODEC_ID_MJPEG||st->codec->codec_id==CODEC_ID_MPEG4||st->codec->codec_id==CODEC_ID_DIRAC||st->codec->codec_id==CODEC_ID_DNXHD||st->codec->codec_id==CODEC_ID_H264){st->codec->time_base=(AVRational){1,25};}av_set_pts_info(st, 64, 1, 1200000);return 0;}原始地读实际的包函数:原始地写包函数:h264混合器:h264分离器:libavformat/allformats.c文件的av_register_all函数注册了h264分离器和混合器:把注册格式函数也贴出来吧:AVInputFormat *first_iformat =NULL;/** head of registered output format linked list */AVOutputFormat *first_oformat =NULL;void av_register_input_format(AVInputFormat*format){AVInputFormat **p;p =&first_iformat;while(*p!=NULL) p =&(*p)->next;*p = format;format->next=NULL;}void av_register_output_format(AVOutputFormat*format){AVOutputFormat **p;p =&first_oformat;while(*p!=NULL) p =&(*p)->next;*p = format;format->next=NULL;}/u3/104564/showart_2369231.html调用av_open_input_file(&pFormatCtx,is->filename,NULL, 0,NULL)函数打开输入的文件.1.分析一下函数原型:int av_open_input_file(AVFormatContext**ic_ptr,// 输出参数: 格式上下文const char*filename,// 文件名AVInputFormat *fmt,// 输入的格式, 为NULL, 即未知int buf_size,// 缓冲的大小, 为0AVFormatParameters *ap);// 格式的参数, 为NULL2.初始化探测数据:AVProbeData probe_data,*pd =&probe_data;pd->filename="";if(filename)pd->filename= filename;pd->buf=NULL;pd->buf_size= 0;3.探测输入的格式:if(!fmt){// fmt == NULL, 成立fmt = av_probe_input_format(pd, 0);}进入av_probe_input_format函数:AVInputFormat *av_probe_input_format(AVProbeData*pd, int is_opened){int score=0;return av_probe_input_format2(pd, is_opened,&score);}进入av_probe_input_format2函数:AVInputFormat *av_probe_input_format2(AVProbeData*pd, int is_opened,int*score_max){AVInputFormat *fmt1,*fmt;int score;fmt =NULL;for(fmt1= first_iformat; fmt1!=NULL; fmt1 = fmt1->next){if(!is_opened==!(fmt1->flags&AVFMT_NOFILE))// is_opened == 0, fmt1->flags 没有设置 AVFMT_NOFILE 标志时成立continue;/* 省略部分代码 */}见libavformat/raw.c文件:AVInputFormat h264_demuxer ={"h264",NULL_IF_CONFIG_SMALL("raw H.264 video format"),0,h264_probe,video_read_header,ff_raw_read_partial_packet,.flags= AVFMT_GENERIC_INDEX,.extensions ="h26l,h264,264",//FIXME remove after writing mpeg4_probe.value = CODEC_ID_H264,};由于 h264_demuxer.flags == AVFMT_GENERIC_INDEX,所以上面成立,continue,返回的 AVInputFormat 指针为NULL, 探测不成功.1. 打开文件:因 fmt == NULL, 上面成立, 再看下面的代码:进入url_fopen函数:进入url_open函数:if(!isalpha(*p))// 如果不是英文字母goto file_proto;if((q- proto_str)<sizeof(proto_str)- 1)*q =*p;// 记录协议字符串p ;}if(*p=='\0'|| is_dos_path(filename)){// 如果上面是因为结束而跳出, 或且文件名是DOS路径file_proto:strcpy(proto_str,"file");// 文件协议}else{*q ='\0';// 追加结束符}up = first_protocol;while(up!=NULL){if(!strcmp(proto_str, up->name))// 协议匹配return url_open_protocol (puc,up, filename, flags);// 用这个协议打开URLup = up->next;}*puc =NULL;return AVERROR(ENOENT);}进入url_open_protocol函数:int url_open_protocol(URLContext **puc,// 输出参数: URL上下文struct URLProtocol*up,// URL协议constchar*filename,// 文件名int flags) // 标志{URLContext *uc;int err;// 网络初始化#if CONFIG_NETWORKif(!ff_network_init())return AVERROR(EIO);#endif// 分配URL上下文并加上文件名的存储空间uc = av_mallocz(sizeof(URLContext) strlen(filename) 1);if(!uc){err = AVERROR(ENOMEM);goto fail;}// 初始化URL上下文#if LIBAVFORMAT_VERSION_MAJOR>= 53uc->av_class=&urlcontext_class;#endif// 记录文件名uc->filename=(char*)&uc[1];strcpy(uc->filename, filename);uc->prot= up; // URL协议uc->flags= flags; // 标志uc->is_streamed= 0; // 默认不是流, 可以在up->url_open函数里修改uc->max_packet_size=0;// 包最大多大, 默认为0,可以在up->url_open函数里修改// 打开URLerr = up->url_open(uc, filename, flags);if(err< 0){av_free(uc);goto fail;}if((flags &(URL_WRONLY | URL_RDWR)) // 如果以可写方式打开||!strcmp(up->name,"file")) // 或且是文件协议// 如果不是流并且不可以url_seekif(!uc->is_streamed&& url_seek(uc, 0,SEEK_SET)<先来看看url_get_max_packet_size函数进入url_fdopen函数:uint8_t*buffer;int buffer_size, max_packet_size;max_packet_size = url_get_max_packet_size(h);if(max_packet_size){buffer_size = max_packet_size;}else{buffer_size =IO_BUFFER_SIZE;// 缓冲大小为IO_BUFFER_SIZE}buffer = av_malloc(buffer_size);// 分配缓冲if(!buffer)return AVERROR(ENOMEM);*s =av_mallocz(sizeof(ByteIOContext));// 分配字节IO上下文if(!*s){av_free(buffer);return AVERROR(ENOMEM);}if(init_put_byte(*s, buffer, buffer_size,(h->flags&URL_WRONLY || h->flags& URL_RDWR), h,url_read,url_write,进入init_put_byte函数:int init_put_byte(ByteIOContext*s,// 字节IO上下文unsigned char*buffer,// 缓冲int buffer_size,// 缓冲的大小int write_flag,// 写标志void*opaque,// URL上下文int(*read_packet)(void*opaque,uint8_t*buf,int buf_size),// 读包int(*write_packet)(void*opaque,uint8_t*buf,int buf_size),// 写包int64_t(*seek)(void*opaque,int64_t offset,int whence))// 调整文件指针{s->buffer= buffer;s->buffer_size= buffer_size;s->buf_ptr= buffer;s->opaque= opaque;url_resetbuf(s, write_flag? URL_WRONLY : URL_RDONLY);s->write_packet= write_packet;s->read_packet= read_packet;s->seek= seek;s->pos = 0;s->must_flush= 0;s->eof_reached= 0;s->error= 0;s->is_streamed= 0;s->max_packet_size= 0;s->update_checksum=NULL;if(!read_packet&&!write_flag){s->pos= buffer_size;s->buf_end= s->buffer buffer_size;}s->read_pause=NULL;s->read_seek=NULL;return 0;}void*logctx=ap&&ap->prealloced_context?*ic_ptr : NULL;// 因为 ap == NULL, 所以 logctx 也 == NULL.if(!fmt&&(err = ff_probe_input_buffer(&pb,&fmt, filename,logctx, 0,logctx ?(*ic_ptr)->probesize: 0))< 0){goto fail;}// fmt == NULL 时才执行 ff_probe_input_buffer 函数, 因为 fmt 就等于NULL, 成立.ff_probe_input_buffer函数的原型:关键的代码片断:get_buffer函数, 有两处比较关键:fill_buffer函数, 有一处比较关键:好了, 到第二次探测输入格式的地方了:进入av_probe_input_format2函数:is_opened,int*score_max){AVInputFormat *fmt1,*fmt;int score;fmt =NULL;for(fmt1= first_iformat; fmt1!=NULL; fmt1 = fmt1->next){ if(!is_opened==!(fmt1->flags& AVFMT_NOFILE))continue;/* 这次is_opened == 1, fmt1->flags设置AVFMT_NOFILE标志才时成立*//* 由于h264_demuxer.flags == AVFMT_GENERIC_INDEX, 所以上面不成立, 继续执行*/score = 0;if(fmt1->read_probe){score =fmt1->read_probe(pd); /* 调用h264_demuxer.h264_probe */}elseif(fmt1->extensions){if(av_match_ext(pd->filename, fmt1->extensions)){ /* 文件名和格式扩展名的匹配 *//* h264_demuxer.extensions = "h26l,h264,264" */score = 50;}}if(score>*score_max){*score_max = score;av_match_ext函数:总算探测到输入格式了.err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);AVFormatParameters default_ap;// 使用缺省的格式参数if(!ap){ap=&default_ap;memset(ap, 0,sizeof(default_ap));}if(!ap->prealloced_context)ic = avformat_alloc_context(); // 分配格式上下文elseic =*ic_ptr;if(!ic){err = AVERROR(ENOMEM);goto fail;}// 初始化格式上下文ic->iformat= fmt; // 格式ic->pb = pb; // 字节IO上下文ic->duration= AV_NOPTS_VALUE;ic->start_time= AV_NOPTS_VALUE;av_strlcpy(ic->filename,filename,sizeof(ic->filename)); // 文件名/* 分配私有数据 */if(fmt->priv_data_size> 0){ic->priv_data= av_mallocz(fmt->priv_data_size);if(!ic->priv_data){err = AVERROR(ENOMEM);goto fail;}}else{ic->priv_data=NULL;}// 读首部if(ic->iformat->read_header){err = ic->iformat->read_header(ic, ap);if(err< 0)goto fail;}// 获得数据偏移if(pb &&!ic->data_offset)ic->data_offset= url_ftell(ic->pb);具体请参看ffmpeg分析系列之三(输入输出格式)格式上下文结构:AV类结构:typedef struct{/*** The name of the class; usually it is the same name as the* context structure type to which the AVClass is associated.*/const char*class_name;/*** A pointer to a function which returns the name of a context* instance ctx associated with the class.*/const char*(*item_name)(void*ctx);/*** a pointer to the first option specified in the class if any or NULL** @see av_set_default_options()*/const struct AVOption *option;/*** LIBAVUTIL_VERSION with which this structure was created.* This is used to allow fields to be added without requiring major* version bumps everywhere.*/int version;}AVClass;进入avformat_alloc_context函数, 分配格式上下文:进入avformat_get_context_defaults函数, 格式获得缺省上下文:av_opt_set_defaults函数就不分析了.下面继续分析:以输入格式为libavformat/raw.c下的h264_demuxer为例:会调用video_read_header函数:return AVERROR(ENOMEM);// 初始化流st->codec->codec_type= AVMEDIA_TYPE_VIDEO; // 编码编码器类型st->codec->codec_id= s->iformat->value; // 为 CODEC_ID_H264st->need_parsing= AVSTREAM_PARSE_FULL; // 需要全分析/* for MJPEG, specify frame rate *//* for MPEG-4 specify it, too (most MPEG-4 streams do not have the fixed_vop_rate set ...)*/if(ap->time_base.num){st->codec->time_base= ap->time_base;}else if( st->codec->codec_id== CODEC_ID_MJPEG||st->codec->codec_id==CODEC_ID_MPEG4||st->codec->codec_id==CODEC_ID_DIRAC||st->codec->codec_id==CODEC_ID_DNXHD||st->codec->codec_id==CODEC_ID_H264){st->codec->time_base=(AVRational){1,25}; // 设置时基}av_set_pts_info(st, 64, 1, 1200000); // 设置PTS(显示时间截)信息return 0;进入av_new_stream函数:st->id = id; // ID, 为0st->start_time= AV_NOPTS_VALUE; // 开始时间st->duration= AV_NOPTS_VALUE;/* we set the current DTS to 0 so that formats without any timestampsbut durations get some timestamps, formats with some unknowntimestamps have their first few packets buffered and thetimestamps corrected before they are returned to the user */st->cur_dts= 0; // 当前的解码时间截st->first_dts= AV_NOPTS_VALUE; // 起始的解码时间截st->probe_packets= MAX_PROBE_PACKETS; // 探测的最大包数/* default pts setting is MPEG-like */av_set_pts_info(st, 33, 1, 90000); // 设置PTS显示时间截信息st->last_IP_pts= AV_NOPTS_VALUE;for(i=0; i<MAX_REORDER_DELAY1; i )st->pts_buffer[i]= AV_NOPTS_VALUE;st->reference_dts= AV_NOPTS_VALUE;st->sample_aspect_ratio=(AVRational){0,1};s->streams[s->nb_streams ]= st; // 记录流, 同时流数加一return st;分配编码解码器上下文:staticconst AVClass av_codec_context_class = {"AVCodecContext",context_to_name,options, LIBAVUTIL_VERSION_INT};void avcodec_get_context_defaults2(AVCodecContext*s, enum AVMediaType codec_type){int flags=0;memset(s, 0,sizeof(AVCodecContext));s->av_class=&av_codec_context_class;s->codec_type= codec_type;if(codec_type== AVMEDIA_TYPE_AUDIO)flags= AV_OPT_FLAG_AUDIO_PARAM;else if(codec_type== AVMEDIA_TYPE_VIDEO)flags= AV_OPT_FLAG_VIDEO_PARAM;else if(codec_type== AVMEDIA_TYPE_SUBTITLE)flags= AV_OPT_FLAG_SUBTITLE_PARAM;av_opt_set_defaults2(s, flags, flags);s->time_base=(AVRational){0,1};s->get_buffer= avcodec_default_get_buffer;s->release_buffer= avcodec_default_release_buffer;s->get_format= avcodec_default_get_format;s->execute= avcodec_default_execute;s->execute2= avcodec_default_execute2;s->sample_aspect_ratio=(AVRational){0,1};s->pix_fmt= PIX_FMT_NONE;s->sample_fmt= SAMPLE_FMT_NONE;s->palctrl=NULL;s->reget_buffer= avcodec_default_reget_buffer;s->reordered_opaque= AV_NOPTS_VALUE;}AVCodecContext *avcodec_alloc_context2(enum AVMediaType codec_type){AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext));if(avctx==NULL)return NULL;avcodec_get_context_defaults2(avctx, codec_type);return avctx;}void avcodec_get_context_defaults(AVCodecContext*s){avcodec_get_context_defaults2(s,AVMEDIA_TYPE_UNKNOWN);}AVCodecContext *avcodec_alloc_context(void){return avcodec_alloc_context2(AVMEDIA_TYPE_UNKNOWN);}。
FFmpeg 转码总结转码参数输入、输出音频、视频编码器、码率、是否帧内压缩转码流程一、处理输入文件1.处理输入文件打开输入文件,获取文件内的流信息2.初始化AVFormatContextAVFormatContext* ic = avformat_alloc_context();3.打开输入文件,获取流信息av_open_input_file(&ic, filename, NULL, 0, NULL);如果成功,ic中包含了输入文件的流信息二、处理输出文件1.初始化 AVFormatContextAVFormatContext* oc = avformat_alloc_context();2.设置输入文件的名称av_strlcpy(oc->filename,filename,sizeof(oc->filename));3.给输出文件增加视频流新建一个流AVStream *st = av_new_stream(oc, oc->nb_streams);4.设置视频编码器的相关参数编码线程,最小为1avcodec_thread_init(st->codec, thread_count);5.设置编码器codec_id=find_codec_or_die(video_codec_name,CODEC_TYPE_VIDEO,1) AVCodec*codec = avcodec_find_encoder((CodecID)codec_id);AVCodecContext *video_enc = st->codec;Video_enc->codec_type = CODEC_TYPE_VIDEO;Video_enc->codec_id = (CodecID)codec_id;//设置帧率 25帧每秒video_enc->time_base.den = 25;video_enc->time_base.num = 1;设置视频宽高video_enc->width = 576;video_enc->height = 420;设置显示横宽比 4:3video_enc->sample_aspect_ratio.num = 4;video_enc->sample_aspect_ratio.den = 3;如果帧内编码video_enc->gop_size = 0;给输出文件增加音频流新建一个音频流AVStream *st = av_new_stream(oc, oc->nb_streams);audio_enc = st->codec;audio_enc->codec_type = CODEC_TYPE_AUDIO;设置音频编码器codec_id = find_codec_or_die(audio_codec_name, CODEC_TYPE_AUDIO, 1);audio_enc->codec_id = (CodecID)codec_id;设置音频参数audio_enc->thread_count = thread_count;audio_enc->channels = audio_channels;audio_enc->channel_layout = channel_layout;const enum SampleFormat *p= codec->sample_fmts;for(; *p!=-1; p++){if(*p == audio_enc->sample_fmt)break;}if(*p == -1)audio_enc->sample_fmt = codec->sample_fmts[0];audio_enc->sample_rate = audio_sample_rate;audio_enc->time_base.num = 1;audio_enc->time_base.den = audio_sample_rate;三、打开编码器和解码器1.avcodec_open(st->codec, codec)2.写输出文件的头信息av_write_header(os)3.循环读取每一帧av_read_frame(is, &pkt)4.计算pts与dtspkt.dts+= av_rescale_q(input_files_ts_offset[ist->file_index],m_AV_Time_Base_Q, ist->st->time_base);pkt.pts+= av_rescale_q(input_files_ts_offset[ist->file_index],m_AV_Time_Base_Q, ist->st->time_base);5.帧解码音频:avcodec_decode_audio2(ist->st->codec, samples,&data_size,ptr, len);视频:avcodec_decode_video(ist->st->codec, &picture, &got_picture, ptr, len);音频重新编码:avcodec_encode_audio(enc, audio_out,audio_out_size,(short *)audio_buf);视频编码:ret = avcodec_encode_video(enc,bit_buffer, bit_buffer_size,&big_picture);6.写入输出文件av_interleaved_write_frame(s, pkt);7.写输出文件结束信息av_write_trailer(os);。
准备工作:1.登录/builds/下载对应的开发版本如xp32位系统下载FFmpeg 32-bit Dev Versions与FFmpeg 32-bit Shared Versions中dev版本里有头文件与静态lib库,在开发的时候包含进工程,share版本里有程序运行发布时候需要的dll动态库。
2.在vs2010中配置inclue与lib工程→属性→配置属性→c/c++→常规→附加包含目录→添加dev版本中的include路径工程→属性→配置属性→链接器→常规→附加库目录→添加dev版本中的lib 路径工程→属性→配置属性→链接器→输入→附加依赖项→添加dev版本中的lib文件,具体包括:avcodec.lib,avformat.lib,avdevice.lib,avfilter.lib,avutil.lib,postproc.lib,swresample.lib,swscale.lib3.在工程中引用ffmpeg头文件,因为ffmepeg是纯C程序,因此,在C++中包含其头文件需要加上extern “C”标识符,如:extern"C"{#include"libavformat/avformat.h"#include"libavcodec/avcodec.h"}4.工程调试运行时需要将dll添加至工程目录,具体包括:avcodec-55.dll,avdevice-55.dll,avfilter-3.dll,avformat-55.dll,avutil-52.dll,postproc-52.dll,swresample-0.dll,swscale-2.dll5.程序编译会报无法打开包括文件:“inttypes.h”: No such file or directory原因是因为VS2010不支持inttypes.h。
解决方法删除,并在之前添加如下代码#if defined(WIN32)&&!defined(__MINGW32__)&&!defined(__CYGWIN__)# define CONFIG_WIN32#endif#if defined(WIN32)&&!defined(__MINGW32__)&&!defined(__CYGWIN__)&&!defined(EMULATE_INTTYPES)#define EMULATE_INTTYPES#endif#ifndef EMULATE_INTTYPES#include#elsetypedef signed char int8_t;typedef signed short int16_t;typedef signed int int32_t;typedef unsigned char uint8_t;typedef unsigned short uint16_t;typedef unsigned int uint32_t;#ifdef CONFIG_WIN32typedef signed__int64int64_t;typedef unsigned__int64uint64_t;#elsetypedef signed long long int64_t;typedef unsigned long long uint64_t;#endif#endif6.打开一个视频文件:首先调用av_register_all()初始化并注册视频文件格式与编解码库,注意只需要初始化一次。
然后调用avformat_open_input()打开具体的视频文件如:AVFormatContext*pFormatCtx=NULL;const char*filename="a.avi";int ret;if((ret=avformat_open_input(&pFormatCtx,filename,NULL,NULL))<0) {abort();}其中avformat_open_input具体实现与参数含义avformat_open_input[cpp]view plaincopy1.//参数ps包含一切媒体相关的上下文结构,有它就有了一切,本函数如果打开媒体成功,2.//会返回一个AVFormatContext的实例.3.//参数filename是媒体文件名或URL.4.//参数fmt是要打开的媒体格式的操作结构,因为是读,所以是inputFormat.此处可以5.//传入一个调用者定义的inputFormat,对应命令行中的 -f xxx段,如果指定了它,6.//在打开文件中就不会探测文件的实际格式了,以它为准了.7.//参数options是对某种格式的一些操作,是为了在命令行中可以对不同的格式传入8.//特殊的操作参数而建的,为了了解流程,完全可以无视它.9.int avformat_open_input(AVFormatContext **ps,10.const char *filename,11. AVInputFormat *fmt,12. AVDictionary **options)13.{14. AVFormatContext *s = *ps;15.int ret = 0;16. AVFormatParameters ap = { { 0 } };17. AVDictionary *tmp = NULL;18.19.//创建上下文结构20.if (!s && !(s = avformat_alloc_context()))21.return AVERROR(ENOMEM);22.//如果用户指定了输入格式,直接使用它23.if (fmt)24. s->iformat = fmt;25.26.//忽略27.if (options)28. av_dict_copy(&tmp, *options, 0);29.30.if ((ret = av_opt_set_dict(s, &tmp)) < 0)31.goto fail;32.33.//打开输入媒体(如果需要的话),初始化所有与媒体读写有关的结构们,比如34.//AVIOContext,AVInputFormat等等35.if ((ret = init_input(s, filename)) < 0)36.goto fail;37.//执行完此函数后,s->pb和s->iformat都已经指向了有效实例.pb是用于读写数据的,它38.//把媒体数据当做流来读写,不管是什么媒体格式,而iformat把pb读出来的流按某种媒体格39.//式进行分析,也就是说pb在底层,iformat在上层.40.41.//很多静态图像文件格式,都被当作一个格式处理,比如要打开.jpeg文件,需要的格式42.//名为image2.此处还不是很了解具体细节,作不得准哦.43./* check filename in case an image number is expected */44.if (s->iformat->flags & AVFMT_NEEDNUMBER) {45.if (!av_filename_number_test(filename)) {46. ret = AVERROR(EINVAL);47.goto fail;48. }49. }50.51. s->duration = s->start_time = AV_NOPTS_VALUE;52.//上下文中保存下文件名53. av_strlcpy(s->filename, filename, sizeof(s->filename));54.55./* allocate private data */56.//为当前格式分配私有数据,主要用于某格式的读写操作时所用的私有结构.57.//此结构的大小在定义AVInputFormat时已指定了.58.if (s->iformat->priv_data_size > 0) {59.if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {60. ret = AVERROR(ENOMEM);61.goto fail;62. }63.//这个可以先不必管它64.if (s->iformat->priv_class) {65. *(const AVClass**) s->priv_data = s->iformat->priv_class;66. av_opt_set_defaults(s->priv_data);67.if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)68.goto fail;69. }70. }71.72./* e.g. AVFMT_NOFILE formats will not have a AVIOContext */73.//从mp3文件中读ID3数据并保存之.74.if (s->pb)75. ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC);76.77.//读一下媒体的头部,在read_header()中主要是做某种格式的初始化工作,比如填充自己的78.//私有结构,根据流的数量分配流结构并初始化,把文件指针指向数据区开始处等.79.if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)80.if ((ret = s->iformat->read_header(s, &ap)) < 0)81.goto fail;82.83.//保存数据区开始的位置84.if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)85. s->data_offset = avio_tell(s->pb);86.87. s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;88.89.if (options) {90. av_dict_free(options);91. *options = tmp;92. }93. *ps = s;94.//执行成功95.return 0;96.97.//执行失败98. fail: av_dict_free(&tmp);99.if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))100. avio_close(s->pb);101. avformat_free_context(s);102. *ps = NULL;103.return ret;104.}init_input[cpp]view plaincopy1.//打开输入媒体并填充其AVInputFormat结构2.static int init_input(AVFormatContext *s, const char *filename)3.{4.int ret;5. AVProbeData pd = { filename, NULL, 0 };6.7.//当调用者已指定了pb(数据取得的方式)--一般不会这样.8.if (s->pb) {9. s->flags |= AVFMT_FLAG_CUSTOM_IO;10.if (!s->iformat)11.//如果已指定了pb但没指定iformat,以pb读取媒体数据进行探测,取得.取得iformat.12.return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0,0);13.else if (s->iformat->flags & AVFMT_NOFILE)14.//如果已指定pb也指定了iformat,但是又指定了不需要文件(也包括URL指定的地址),这就矛盾了,15.//此时应是不需要pb的,因为不需操作文件,提示一下吧,也不算错.16. av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and"17."will be ignored with AVFMT_NOFILE format.\n");18.return 0;19. }20.21.//一般会执行到这里22.if ((s->iformat && s->iformat->flags & AVFMT_NOFILE)23. || (!s->iformat && (s->iformat = av_probe_input_format(&pd, 0))))24.//如果已指定了iformat并且不需要文件,也就不需要pb了,可以直接返回25.//如果没指定iformat,但是可以从文件名中猜出iformat,也成功.26.return 0;27.28.//如果从文件名中也猜不出媒体格式,则只能打开这个文件进行探测了,先打开文件29.if ((ret = avio_open(&s->pb, filename, AVIO_FLAG_READ)) < 0)30.return ret;31.if (s->iformat)32.return 0;33.//再探测之34.return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);35.}avio_open[cpp]view plaincopy1.//打开一个地址指向的媒体2.int avio_open(AVIOContext **s, const char *filename, int flags)3.{4.//URLContext代表一个URL地址指向的媒体文件,本地路径也算一种.它封装了5.//操作一个媒体文件的相关数据,最重要的是prot变量,是URLProtocol型的.6.//prot代表一个特定的协义和协议操作函数们,URLContext包含不同的prot,7.//就可以通过URLContext使用不同的协议读写媒体数据,比如tcp,http,本地8.//文件用file协议.9. URLContext *h;10.int err;11.12.//创建并初始化URLContext,其prot通过文件名确定.然后打开这个媒体文件13. err = ffurl_open(&h, filename, flags);14.if (err < 0)15.return err;16.//其实文件已经在上边真正打开了.这里只是填充AVIOContext.使它记录下17.//URLContext,以及填充读写数据的函数指针.18. err = ffio_fdopen(s, h);19.if (err < 0) {20. ffurl_close(h);21.return err;22. }23.return 0;24.}av_probe_input_buffer[cpp]view plaincopy1.int av_probe_input_buffer(AVIOContext *pb,2. AVInputFormat **fmt,3.const char *filename,4.void *logctx,5. unsigned int offset,6. unsigned int max_probe_size)7.{8. AVProbeData pd = { filename ? filename : "", NULL, -offset };9. unsigned char *buf = NULL;10.int ret = 0, probe_size;11.12.//计算最多探测数据的字节数13.if (!max_probe_size) {14. max_probe_size = PROBE_BUF_MAX;15. } else if (max_probe_size > PROBE_BUF_MAX) {16. max_probe_size = PROBE_BUF_MAX;17. } else if (max_probe_size < PROBE_BUF_MIN) {18.return AVERROR(EINVAL);19. }20.21.if (offset >= max_probe_size) {22.return AVERROR(EINVAL);23. }24.25.//循环直到探测完指定的数据26.for (probe_size = PROBE_BUF_MIN;27. probe_size <= max_probe_size && !*fmt;28. probe_size =29. FFMIN(probe_size<<1, FFMAX(max_probe_size, probe_size+1))) {30.int score = probe_size < max_probe_size ? AVPROBE_SCORE_MAX / 4 : 0;31.int buf_offset = (probe_size == PROBE_BUF_MIN) ? 0 : probe_size >> 1;32.void *buftmp;33.34.if (probe_size < offset) {35.continue;36. }37.38./* read probe data */39.//分配读取数据存放的缓冲40. buftmp = av_realloc(buf, probe_size + AVPROBE_PADDING_SIZE);41.if (!buftmp) {42. av_free(buf);43.return AVERROR(ENOMEM);44. }45. buf = buftmp;46.//利用pb读数据到缓冲的剩余空间中47.if ((ret = avio_read(pb, buf + buf_offset, probe_size - buf_offset))48. < 0) {49./* fail if error was not end of file, otherwise, lower score */50.if (ret != AVERROR_EOF) {51. av_free(buf);52.return ret;53. }54. score = 0;55. ret = 0; /* error was end of file, nothing read */56. }57. pd.buf_size += ret;58. pd.buf = &buf[offset];59.60.//缓冲中没有数据的部分要清061. memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);62.63./* guess file format */64.//从一个打开的文件只探测媒体格式65. *fmt = av_probe_input_format2(&pd, 1, &score);66.if (*fmt) {67.if (score <= AVPROBE_SCORE_MAX / 4) { //this can only be true inthe last iteration68. av_log(69. logctx,70. AV_LOG_WARNING,71."Format %s detected only with low score of %d, misdetection possible!\n",72. (*fmt)->name, score);73. } else74. av_log(logctx, AV_LOG_DEBUG,75."Format %s probed with size=%d and score=%d\n",76. (*fmt)->name, probe_size, score);77. }78.//不成功,继续79. }80.81.if (!*fmt) {82. av_free(buf);83.return AVERROR_INVALIDDATA;84. }85.86./* rewind. reuse probe buffer to avoid seeking */87.//把探测时读入的数据保存到pb中,为的是真正读时直接利用之.88.if ((ret = ffio_rewind_with_probe_data(pb, buf, pd.buf_size)) < 0)89. av_free(buf);90.91.return ret;92.}7.获取流信息int videoStreamIndex=0;for(;videoStreamIndex<pFormatCtx->nb_streams;++videoStreamIndex) {if(pFormatCtx->streams[videoStreamIndex]->codec->coder_type== AVMEDIA_TYPE_VIDEO){break;}}if(videoStreamIndex==pFormatCtx->nb_streams){//没有找到视频流abort();}8.解码视频AVCodecContext*pCodeCtx=pFormatCtx->streams[videoStreamIndex]->codec;//获?取¨?解a码?器¡ÂAVCodec*pCodec=avcodec_find_decoder(pCodeCtx->codec_id);if(pCodec==NULL){abort();}if(pCodec->capabilities&CODEC_CAP_TRUNCATED){pCodeCtx->flags|=CODEC_FLAG_TRUNCATED;}if(avcodec_open2(pCodeCtx,pCodec,NULL)<0){abort();}//给解码后视频帧预分配存储空间?AVFrame*pFrame=NULL;pFrame=avcodec_alloc_frame();//预分配存储空间用来存储转换后的帧?AVFrame*pFrameRGB=NULL;pFrameRGB=avcodec_alloc_frame();int numBytes=avpicture_get_size(PIX_FMT_RGB24,pCodeCtx->width,pCodeCtx->height);//指向的内存关联起来uint8_t*buffer=(uint8_t*)av_malloc(numBytes);avpicture_fill((AVPicture*)pFrameRGB,buffer,PIX_FMT_RGB24,pCodeCtx->width,pCodeCtx->height);AVPacket pPacket;int num=0;char*fileEx=".bmp";while(av_read_frame(pFormatCtx,&pPacket)>=0){//判断是否为视频流if(pPacket.stream_index==videoStreamIndex){int decFinished;avcodec_decode_video2(pCodeCtx,pFrame,&decFinished,&pPacket);if(decFinished){struct SwsContext*img_convert_ctx=NULL;img_convert_ctx=sws_getCachedContext(img_convert_ctx,pCodeCtx->width,pCodeCtx->height,pCodeCtx->pix_fmt,pCodeCtx->width,pCodeCtx->height,PIX_FMT_RGB24,SWS_ BICUBIC,NULL,NULL,NULL);if(img_convert_ctx==NULL){abort();}//将YUV12转换为RGB24sws_scale(img_convert_ctx,(const uint8_t*const*)pFrame->data,pFrame->linesize,0,pCodeCtx->height,pFrameRGB->data,pFrameRGB->linesize);char temp[10];_itoa_s(num,temp,10);char path[256]="bmp/";strcat_s(path,sizeof(path),temp);strcat_s(path,sizeof(path),fileEx);++num;BMP_SaveFile(path,pFrameRGB->data[0],pCodeCtx->width,pCodeCtx->height ,24);}}av_free_packet(&pPacket);}//回收资源av_free(buffer);av_free(pFrameRGB);av_free(pFrame);avcodec_close(pCodeCtx);avformat_close_input(&pFormatCtx);return0;}bool BMP_SaveFile(const char*szFile,const void*pBmp,int width,int height, int bitCount){FILE*pFile=NULL;fopen_s(&pFile,szFile,"wb");ASSERT(pFile!=NULL);int bmp_size=width*height*(bitCount/8);BITMAPFILEHEADER bmpHeader;bmpHeader.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) +bmp_size;bmpHeader.bfType=0x4D42;bmpHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);bmpHeader.bfReserved1=0;bmpHeader.bfReserved2=0;fwrite(&bmpHeader,sizeof(bmpHeader),1,pFile);BITMAPINFOHEADER bmiHeader;bmiHeader.biSize=sizeof(bmiHeader);bmiHeader.biWidth=width;bmiHeader.biHeight=height;bmiHeader.biPlanes=1;bmiHeader.biBitCount=bitCount;bmiHeader.biCompression=0;bmiHeader.biSizeImage=bmp_size;bmiHeader.biXPelsPerMeter=0;bmiHeader.biYPelsPerMeter=0;bmiHeader.biClrUsed=0;bmiHeader.biClrImportant=0;fwrite(&bmiHeader,sizeof(bmiHeader),1,pFile);reverseChar((char**)&pBmp,bmp_size);fwrite(pBmp,bmp_size,1,pFile);fclose(pFile);return true;}void reverseChar(char**strs,int nSize){//定义一个指针指向strs的内存地址char*p=*strs;char tmp;for(unsigned int i=0;i<nSize/2;i++){tmp=*(p+i);*(p+i)=*(p+nSize-1-i);*(p+nSize-1-i)=tmp;}}。