ffmpeg学习四:写第一个程序-视频解封装与解码
2016-12-01 16:35
531 查看
前面通过阅读《FFmpeg Basic》这本书,对ffmpeg工程和视频编解码的基本知识有了一定的理解,学习编程最重要的当然是动手实践了,所以这片博客,我将会完整记录自己第一次编写视频解码程序的过程。 这个程序能将一个视频转换为一帧一帧的图片。这个程序参考了decoding_encoding.c文件,但使它们还是有很大的不同。
我当时编译ffmepg源码是这样的:
1.执行./configure –disable-yasm –enable-shared –prefix=tmp
2.make
3.make install
因此,编译完成后,头文件和库都在tmp目录下,那我现在编译程序需要头文件和苦呀,所以都需要从这里去找。因此,我们写一个makefile和一个Init.sh脚本,init.sh脚本只有一行,就是告诉需要运行的程序,动态链接库去哪里找,init.sh脚本如下:
makefile如下:
这个Makefile非常简单,我们这里编译的是bin:avio_reading.c这个例子。如果对Makefile稍作了解,这个Makefile一眼就能看懂做了什么事情吧,所以就不废话了。
执行make后,会生成avio_reading.bin文件,执行这个文件:
./avio_reading.bin hello.mp4
打印如下:
好了,现在我们可以编译我们自己的code了,接下来我们参考decoding_encoding.c文件,把hello.mp4这个视频解码成一帧一帧的图片。
这张图是转码的流程图,先把输入文件解码,再重新编码。我们的程序只涉及解码,因此大家只需理解前面三个框就好了。
输入文件->解复用->解编码(得到图片)。
程序的步骤:
step 1:open file,get format info from file header
step 2:get stread info
step 3:find vido stream
step 4:find decoder
step 5:get one instance of AVCodecContext,decode need it.
step 6: open codec
step 7:read frame
step 8:save frame
代码中就是这样注释的,这里只是提取来,方便梳理逻辑。
step1-step3其实就是解复用。
step4-step7其实就是解编码
step8把得到的图像保存下来
下面来安具体的程序
编译完成后, ./decoding_mp4.bin hello.mp4,就会生成一系列的hello%2d.ppm的图片。
大家不要被名字迷惑了,虽然这里的名字是解码mp4文件,而且我们一开始的目标也是解码mp4文件,但是它是可以解码其他格式的视频的,大家可以试试。
编译自己的程序
在编写程序之前,先搭建一下编译环境。我当时编译ffmepg源码是这样的:
1.执行./configure –disable-yasm –enable-shared –prefix=tmp
2.make
3.make install
因此,编译完成后,头文件和库都在tmp目录下,那我现在编译程序需要头文件和苦呀,所以都需要从这里去找。因此,我们写一个makefile和一个Init.sh脚本,init.sh脚本只有一行,就是告诉需要运行的程序,动态链接库去哪里找,init.sh脚本如下:
export LD_LIBRARY_PATH=/home/jinwei/work/ffmpeg-2.7.6/tmp/lib
makefile如下:
VAR_INCLUDE := -I ../../../ffmpeg-2.7.6/tmp/include VAR_SHARED_LIB_DIR = -L ../../../ffmpeg-2.7.6/tmp/lib VAR_SHARED_LIBS = -l avcodec -l avformat -l avdevice -l avutil -l swresample -l avfilter -l swscale avio_reading.bin:avio_reading.o gcc -o $@ $^ $(VAR_INCLUDE) $(VAR_SHARED_LIB_DIR) $(VAR_SHARED_LIBS) %.o:%.c gcc -c $< $(VAR_INCLUDE) $(VAR_SHARED_LIB_DIR) $(VAR_SHARED_LIBS) clean: rm -rf *.o *.bin
这个Makefile非常简单,我们这里编译的是bin:avio_reading.c这个例子。如果对Makefile稍作了解,这个Makefile一眼就能看懂做了什么事情吧,所以就不废话了。
执行make后,会生成avio_reading.bin文件,执行这个文件:
./avio_reading.bin hello.mp4
打印如下:
ptr:0x7f1aacc26000 size:167773 ptr:0x7f1aacc27000 size:163677 ptr:0x7f1aacc29d65 size:152056 ptr:0x7f1aacc2ad65 size:147960 ptr:0x7f1aacc2bd65 size:143864 ptr:0x7f1aacc2cfe7 size:139126 ptr:0x7f1aacc2dfe7 size:135030 ptr:0x7f1aacc2efe7 size:130934 ptr:0x7f1aacc313c7 size:121750 ptr:0x7f1aacc34752 size:108555 ptr:0x7f1aacc380c6 size:93847 ptr:0x7f1aacc3a019 size:85828 ptr:0x7f1aacc3b3a9 size:80820 ptr:0x7f1aacc3c3a9 size:76724 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'hello.mp4': Metadata: major_brand : mp42 minor_version : 0 compatible_brands: isommp42 creation_time : 2016-09-17 11:33:49 Duration: 00:00:20.45, start: 0.000000, bitrate: N/A Stream #0:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1920x1080, 64 kb/s, SAR 1:1 DAR 16:9, 3.72 fps, 60 tbr, 90k tbn, 120 tbc (default) Metadata: creation_time : 2016-09-17 11:33:49 handler_name : VideoHandle
好了,现在我们可以编译我们自己的code了,接下来我们参考decoding_encoding.c文件,把hello.mp4这个视频解码成一帧一帧的图片。
解码流程总结
流程图:这张图是转码的流程图,先把输入文件解码,再重新编码。我们的程序只涉及解码,因此大家只需理解前面三个框就好了。
输入文件->解复用->解编码(得到图片)。
程序的步骤:
step 1:open file,get format info from file header
step 2:get stread info
step 3:find vido stream
step 4:find decoder
step 5:get one instance of AVCodecContext,decode need it.
step 6: open codec
step 7:read frame
step 8:save frame
代码中就是这样注释的,这里只是提取来,方便梳理逻辑。
step1-step3其实就是解复用。
step4-step7其实就是解编码
step8把得到的图像保存下来
下面来安具体的程序
编写程序
//created by Jinwei Liu #include <math.h> #include <libavutil/opt.h> #include <libavcodec/avcodec.h> #include <libavutil/channel_layout.h> #include <libavutil/common.h> #include <libavutil/imgutils.h> #include <libavutil/mathematics.h> #include <libavutil/samplefmt.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> void saveFrame(AVFrame* pFrame, int width, int height, int iFrame,const char *outname) { FILE *pFile; char szFilename[32]; int y; // Open file sprintf(szFilename, outname, iFrame); pFile = fopen(szFilename, "wb"); if (pFile == NULL) return; // Write header fprintf(pFile, "P6\n%d %d\n255\n", width, height); // Write pixel data for (y = 0; y < height; y++) fwrite(pFrame->data[0] + y*pFrame->linesize[0], 1, width * 3, pFile); // Close file fclose(pFile); } static void video_decode_example(const char *outfilename, const char *filename) { AVFormatContext* pFormatCtx = NULL; //step 1:open file,get format info from file header if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0){ fprintf(stderr,"avformat_open_input"); return; } //step 2:get stread info if (avformat_find_stream_info(pFormatCtx, NULL) < 0){ fprintf(stderr,"avformat_find_stream_info"); return; } //just output format info of input file av_dump_format(pFormatCtx, 0, filename, 0); int videoStream = -1; //step 3:find vido stream for (int i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if (videoStream == -1){ fprintf(stderr,"find video stream error"); return; } AVCodecContext* pCodecCtxOrg = NULL; AVCodecContext* pCodecCtx = NULL; AVCodec* pCodec = NULL; pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context //step 4:find decoder pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id); if (!pCodec){ fprintf(stderr,"avcodec_find_decoder error"); return; } //step 5:get one instance of AVCodecContext,decode need it. pCodecCtx = avcodec_alloc_context3(pCodec); if (avcodec_copy_context(pCodecCtx, pCodecCtxOrg) != 0){ fprintf(stderr,"avcodec_copy_context error"); return; } //step 6: open codec if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){ fprintf(stderr,"avcodec_open2 error"); return; } AVFrame* pFrame = NULL; AVFrame* pFrameRGB = NULL; pFrame = av_frame_alloc(); pFrameRGB = av_frame_alloc(); int numBytes = 0; uint8_t* buffer = NULL; numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t)); avpicture_fill((AVPicture*)pFrameRGB, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); struct SwsContext* sws_ctx = NULL; sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); AVPacket packet; int i = 0; //step 7:read frame while (av_read_frame(pFormatCtx, &packet) >= 0) { int frameFinished = 0; avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); if (frameFinished) { sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); if (++i <= 5) { //step 8:save frame saveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i,outfilename); } } } //release resource av_free_packet(&packet); av_free(buffer); av_frame_free(&pFrameRGB); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avcodec_close(pCodecCtxOrg); avformat_close_input(&pFormatCtx); } int main(int argc, char **argv) { const char *output_type; /* register all the codecs */ av_register_all(); if (argc < 2) { printf("usage: %s input_file\n" "example: ./decoding_mp4.bin hello.mp4", argv[0]); return 1; } video_decode_example("hello%02d.ppm", argv[1]); return 0; }
编译完成后, ./decoding_mp4.bin hello.mp4,就会生成一系列的hello%2d.ppm的图片。
大家不要被名字迷惑了,虽然这里的名字是解码mp4文件,而且我们一开始的目标也是解码mp4文件,但是它是可以解码其他格式的视频的,大家可以试试。
相关文章推荐
- 学习的第一个FFmpeg-demo---解封装且解码视频后存储为yuv和h.264文件
- [ffmpeg]视频编解码学习之一:理论基础
- 【ffmpeg学习】Tutorial 04: Spawning Threads 将解析线程与视频解码线程分开
- 学习FFmpeg API – 解码视频
- Android音视频学习第1章:使用ffmpeg进行视频解码
- FFmpeg学习1:视频解码
- FFMPEG之视频编解码基础学习笔记
- 学习FFmpeg API – 解码视频
- FFmpeg学习1:视频解码
- FFmpeg学习1:视频解码
- 【ffmpeg学习】Tutorial 04: Spawning Threads 将解析线程与视频解码线程分开
- ffmpeg 重写tutorial01程序--将一个视频文件解码输出bmp和jpg文件
- [原]零基础学习视频解码之安装ffmpeg
- windows下多线程学习笔记3--ffmpeg两路视频解码
- [学习笔记]基于ffmpeg的视频解码,输出YUV图像到文件。
- 学习FFmpeg API – 解码视频
- [原]零基础学习视频解码之安装ffmpeg
- FFmpeg 学习之 定时器解码两路视频并进行对比
- 从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片
- FFMPEG学习----解码视频