您的位置:首页 > 其它

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脚本如下:

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文件,但是它是可以解码其他格式的视频的,大家可以试试。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: