您的位置:首页 > 其它

[3] ffmpeg基础知识以及使用示例

2016-10-01 10:41 465 查看
* 学习自雷神的博客和视频,图片来自他的课件,雷神博客:*

http://blog.csdn.net/leixiaohua1020/

日期:2016.10.1

作者:isshe

github:github.com/isshe

邮箱:i.sshe@outlook.com

ffmpeg的基础知识

ffmpeg的库

avdecoc: 编解码。

avformat: 封装格式的处理(flv,avi,mov)

swscale: 视频像素数据格式转换(常用于解码后视频的裁剪)。

avutil: 工具库。

avfilter: 滤镜特效处理。

avdevice: 各种设备的输入输出。

postproc: 后加工。

swresample: 音频采样数据格式转换。

前面四个是最常用的

ffmpeg的执行流程



* avcodec_decode_open2()这个函数是解码函数, 最主要的一个函数。

* 图中解码流程是:获取一个pakcet, 然后调用解码函数,把AVPacket结构中的data转换为AVFrame结构的data。

* AVPacket结构存储一帧压缩的编码数据。

* AVFrame结构存储一帧解码后的像素数据(对音频则是采样数据)

* AVFrame结构的元素data是双重指针,YUV数据来说包含data[0],data[1],data[2]分别存Y、U、V数据,注意每帧中U、V数据是Y数据的四分之一大小(对420P来说)。『Y:亮度数据, U,V:色差数据,由于人的眼睛对亮度更敏感,故而YUV数据中存更多的Y而减少UV的数据。当只有Y数据的时候,显示为黑白』

* 解码出来的数据可能函数无效像素。需要用sws_scale()函数处理(大概也可自编写函数处理)

* 如图:

*


* ffmpeg的函数简介:待更新(不定期更新)

ffmpeg解码相关数据结构



* AVFormatContext是一个统筹全局的结构, 包含一些视频文件名,视频时长,视频码率等封装格式信息。

* AVInputFormat包含一些具体的视频格式信息,每种视频格式对应一个这个结构。

* 一般来说视频文件有两个流:视频流和音频流。有几个流就有几个AVStream数据结构, 一般视频流的index==0(也有其他情况), AVStream在AVFormatContext中是一个双重指针。

* AVCodecContext包含像素等编解码信息(对于视频)。

* AVCodec每种视/音频对应一个该结构体(例如h264).

* ffmpeg相关数据结构:待更新(不定期更新)

使用示例

代码:

代码中打印相关结构信息的函数,删减了。

函数的调用流程如前所述。

需要注意,什么地方是什么数据。裸流数据在哪里输出,YUV数据在哪里输出。

输出YUV数据要注意Y数据是UV的4倍,data有3个。

找视频流的时候,需要一个一个流遍历,虽然一般index==0的那个是,但是也有例外。

*

#include <stdio.h>
//#include <libavformat/avformat.h>

#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>     //裁剪
}

//输入和输出的文件名,解码前的数据放H264文件中,解码后放YUV文件中
#define FILENAME            "cuc_ieschool.flv"
#define FILENAME_YUV        "cuc_ieschool_512x288.yuv"
#define FILENAME_H264       "cuc_ieschool_512x288.H264"
#define FILENAME_INFO       "cuc_ieschool.info"

void print_AVFormatContext_info(AVFormatContext *pFormatCtx, FILE *file_stream);

int main(int argc, char *argv[])
{

AVFormatContext    *pFormatCtx;        //统筹结构,保存封装格式相关信息
AVCodecContext     *pCodecCtx;         //保存视/音频编解码相关信息
AVCodec            *pCodec;            //每种编解码器对应一个结构体
AVFrame            *pFrame;            //解码后的结构
AVFrame            *pFrameYUV;
AVPacket           *pPacket;           //解码前
struct SwsContext  *img_convert_ctx;   //头文件只有一行,但是实际上这个结构体十分复杂
int                i = 0;
int                video_index = 0;     //为了检查哪个流是视频流,保存流数组下标
int                y_size = 0;         //
int                ret = 0;            //
int                got_picture = 0;    //
char               filename[256] = FILENAME;  //
int                frame_cnt;          //帧数计算
uint8_t            *out_buffer;        //???
FILE               *fp = NULL;
FILE               *YUVfp = NULL;

if (argc == 2)
{
memcpy(filename, argv[1], strlen(argv[1])+1);
}
else if (argc > 2)
{
printf("Usage: ./*.out video_filename");
}

av_register_all();         //注册所有组件

avformat_network_init();   //初始化网络,貌似暂时没用到,可以试试删除

pFormatCtx = avformat_alloc_context(); //分配内存

//打开文件, 注意第一个参数是指针的指针
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
{
printf("Couldn't open input stream.\n");
return (-1);
}

//获取流信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
printf("Couldn't find stream info\n");
return (-1);
}

//找出视频流, nb_streams表示视频中有几种流
video_index = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_index = i;
break;
}
}
if (-1 == video_index)
{
printf("Couldn't find a video stream\n");
return (-1);
}

//复制编解码的信息到编解码的结构AVCodecContext结构中,
//一方面为了操作结构中数据方便(不需要每次都从AVFormatContext结构开始一个一个指向)
//另一方面方便函数的调用
pCodecCtx = pFormatCtx->streams[video_index]->codec;    //可以查看源码avformat.h中定义的结构

//找到一个和视频流对应的解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (NULL == pCodec)
{
printf("Couldn't found a decoder\n");
return (-1);
}

//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("Couldn't open decoder\n");
return (-1);
}

//输出一些想要输出的信息
fp = stdout;
fp = fopen(FILENAME_INFO, "w+");
if (fp == NULL)
{
fprintf(stderr, "fopen error\n");
return (-1);
}

//输出结构中的信息以更了解这些结构。这里代码只示例一个。
print_AVFormatContext_info(pFormatCtx, fp);

//输出视频文件信息
printf("------------------------file infomation-----------------------------\n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("--------------------------------------------------------------------\n");

pFrame     = av_frame_alloc();
pFrameYUV  = av_frame_alloc();
out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer,
AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

//???
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

pPacket = (AVPacket *)av_malloc(sizeof(AVPacket));
frame_cnt = 0;         //帧数计算吗?

//打开文件
fp = fopen(FILENAME_H264, "w+");
if (fp == NULL)
{
fprintf(stderr, "h264 fopen error\n");
return (-1);
}

YUVfp = fopen(FILENAME_YUV, "w+");
if (NULL == YUVfp)
{
fprintf(stderr, "YUV fopen error\n");
return (-1);
}

while(av_read_frame(pFormatCtx, pPacket) >= 0)
{
if (pPacket->stream_index == video_index)
{
//这里可以输出H264马流信息,这里是解码前
fwrite(pPacket->data, pPacket->size, 1, fp);

//解码
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, pPacket);
if (ret < 0)
{
printf("Decode fail\n");
return (-1);
}

if (got_picture == 0)
{
printf("get picture error\n");
}
else
{
//调整解码出来的图像,解码出来的可能含有填充的信息。
sws_scale(img_convert_ctx, (const uint8_t * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);
//                  printf("Decoded frame index: %d\n", frame_cnt);

// 已经解码,可以输出YUV的信息
// 这个data是一个数组,需要注意!一般3个:代表Y、U、V
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fwrite(pFrameYUV->data[0], 1, pCodecCtx->width * pCodecCtx->height, YUVfp);
fwrite(pFrameYUV->data[1], 1, pCodecCtx->width * pCodecCtx->height / 4, YUVfp);
fwrite(pFrameYUV->data[2], 1, pCodecCtx->width * pCodecCtx->height / 4, YUVfp);
frame_cnt++;
}
}
av_free_packet(pPacket);
}

printf("Decoded frame count: %d\n", frame_cnt);

fclose(fp);
fclose(YUVfp);
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);

return 0;
}

void print_AVFormatContext_info(AVFormatContext *pFormatCtx, FILE *file_stream)
{
fprintf(file_stream, "-------------------------AVFormatContext--------------------------\n");
fprintf(file_stream, "ctx_flags = %d\n"
"nb_streams = %u\n"
"filename = %s\n"
"start_time = %d\n"
"duration = %d\n"
"bit_rate = %d\n"
"packet_size = %u\n"
"max_delay = %d\n"
"flags = %d\n"
"probesize = %d\n"
"key = %d\n"
"keylen = %d\n"
"nb_programs = %d\n",
pFormatCtx->ctx_flags, pFormatCtx->nb_streams,
pFormatCtx->filename, pFormatCtx->start_time,
pFormatCtx->duration, pFormatCtx->bit_rate,
pFormatCtx->packet_size, pFormatCtx->max_delay,
pFormatCtx->flags, pFormatCtx->probesize,
pFormatCtx->key, pFormatCtx->keylen, pFormatCtx->nb_programs);
fprintf(file_stream, "------------------------------------------------------------------\n");
}


编译

这个要注意,ubuntu下弄了好久。

这里要用g++, 因为用到的库x265中,用gcc编不过。

这份代码有很多警告,暂未处理。

g++ ffmpeg_decoder.c -o ffmpeg_decoder.out -O2 -Wall -g \
-lavformat -lavcodec -lavformat -lavutil -lswresample -lswscale \
-lx264 -lx265 -lvpx -lmp3lame -lopus -lfdk-aac -lX11 -lva -lvdpau -lva-drm \
-lva-x11 -lvorbisenc -lvorbis -ltheoraenc -ltheoradec -ldl -lm -lpthread -lz


ubuntu16.04中需要包含这个库才能不出现“未定义的引用”。

结果

相关结构的信息存到.info文件,裸流数据存到h264文件,解码后数据存yuv文件。


参考资料

雷神的视频,课件。

博客:http://blog.csdn.net/leixiaohua1020/article/details/15811977/

视频:http://blog.csdn.net/leixiaohua1020/article/details/47068015

代码下载

http://download.csdn.net/detail/i_scream_/9644386
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ffmpeg 播放器 视频