> 文档中心 > Tiny4412移植ffmpeg实现视频解码

Tiny4412移植ffmpeg实现视频解码


Tiny4412移植ffmpeg实现视频解码

  FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

1.硬件平台

硬件平台:Tiny4412(Cortex-A9)
交叉编译器:arm-linux-gcc(Ver4.5.1)
开发板系统:linux3.5
ffmpeg版本:ffmpeg-4.2.5

2.ffmpeg源码编译

  安装ffmpeg库之前需要先安装x264库。

 2.1 x264编译安装

  x264下载地址:https://www.videolan.org/developers/x264.html
在这里插入图片描述
  安装x264

#解压tar xvf /mnt/hgfs/ubuntu/software_pack/x264-master.tar.bz2#配置信息,生成Makefile./configure --prefix=$PWD/_install --enable-shared --disable-asm --host=arm-linux#修改config.mak gedit config.mak 

在这里插入图片描述
  编译 make
在这里插入图片描述

若编译出现报错:undefined reference to `clock_gettime’,则添加动态链接: -lrt
  重新编译 make

/*编译*/make && make install/*生成文件*/[wbyq@wbyq x264-master]$ tree _install/_install/├── bin│   └── x264├── include│   ├── x264_config.h│   └── x264.h└── lib    ├── libx264.so -> libx264.so.164    ├── libx264.so.164    └── pkgconfig └── x264.pc4 directories, 6 files

 2.1 ffmpeg编译安装

  下载地址:http://www.ffmpeg.org/download.html
在这里插入图片描述
  编译安装ffmpeg

/*解压*/tar xvf /mnt/hgfs/ubuntu/software_pack/ffmpeg-4.2.5.tar.bz2 /*配置信息,生成makefile文件*/./configure --enable-shared --enable-static --prefix=$PWD/_install --cross-prefix=arm-linux- --arch=arm --target-os=linux --enable-gpl --extra-cflags=-I/home/wbyq/tiny4412_pack/x264-master/_install/include --extra-ldflags=-L/home/wbyq/tiny4412_pack/x264-master/_install/lib --enable-ffmpeg --enable-libx264/*编译源码*/make && make install

3.视频解码

#include #include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FILE_NAME "1.mp4"typedef unsigned char u8;typedef unsigned short u16;typedef unsigned int u32;extern const unsigned char ascii_32_16[][32*16/8];static  struct fb_var_screeninfo vinfo;/*lcd可变形参结构体*/static struct fb_fix_screeninfo finfo;/*lcd固定参数结构体*/static unsigned char *lcd_p;/*lcd缓冲区首地址*/u32 lcd_widht;/*LCD屏宽度*/u32 lcd_hight;/*LCD屏高度*/static unsigned char *lcd_p2;/*图像数据*/u32 map_w;u32 map_h;/*画点函数*/void LCD_DrawPoint(int x,int y,int c){unsigned int *p=(unsigned int *)(y*finfo.line_length+x*vinfo.bits_per_pixel/8+lcd_p);*p=c;}/*画点函数*/void LCD_DrawPoint2(int x,int y,int c){unsigned int *p=(unsigned int *)(y*map_w*3+x*3+lcd_p2);*p=c;}/*显示数据*/void LCD_DisplayData(int x,int y,int w,int h,const unsigned char *data){int i,j;int x0=x;unsigned char temp;for(i=0;i<h*w/8;i++){temp=data[i];for(j=0;j<8;j++){if(temp&0x80)LCD_DrawPoint2(x0,y,0xff0000);temp<<=1;x0++;}if(x0-x==w){x0=x;y++;}}}/*显示字符串/void NT35310_DisplayStr(u16 x,u16 y,u8 size,const u8 *str){  while(*str!='\0')  {    if(*str>0x80)    {str+=2;    }    else    {      LCD_DisplayData(x,y,size/2,size,ascii_32_16[*str-' ']);      str++;      x+=size/2;    }  }}/*显示图片函数(居中显示)*/void LCD_Image(int map_w,int map_h,u8 *map_rgb){    u32 w=map_w;//图片宽度    u32 h=map_h;//图片高度    //printf("w=%d,h=%d\n",w,h);   // u16 x=(lcd_widht-w)/2;  //  u16 y=(lcd_hight-h)/2;u16 x=0;u16 y=0;    int i,j;    int cnt=0;    u32 rgb=0xff0000;    u8 *image_rgb=map_rgb;    u32 *p=(unsigned int *)(y*finfo.line_length+x*vinfo.bits_per_pixel/8+lcd_p);//获取到坐标地址    for(i=0;i<h;i++)    { for(j=0;j<w*3;j+=3) {     rgb=(image_rgb[i*w*3+j+2]<<16)|(image_rgb[i*w*3+j+1]<<8)|(image_rgb[i*w*3+j+0]);     //printf("%#x ",rgb);     //printf("%#x ",image_rgb[i*w*3+j]);     p[cnt++]=rgb;      } p+=(finfo.line_length)/sizeof(int);//换到下一行 cnt=0;    }}int video_decode(const char *file);//视频解码int main(){int fd=open("/dev/fb0",2);if(fd<0){printf("打开LCD设备失败\n");return 0;}/*获取屏幕可变参数*/ioctl(fd,FBIOGET_VSCREENINFO,&vinfo);printf("屏幕尺寸:%d * %d\n",vinfo.xres,vinfo.yres);printf("颜色位数:%d\n",vinfo.bits_per_pixel);/*获取固定参数*/ioctl(fd,FBIOGET_FSCREENINFO,&finfo);printf("屏幕显存大小:%d\n",finfo.smem_len);printf("一行的字节数:%d\n",finfo.line_length);lcd_widht=vinfo.xres;lcd_hight=vinfo.yres; /*将缓冲区映射到进程空间*/lcd_p=mmap(NULL,finfo.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);close(fd);if(lcd_p==(void *)-1){printf("内存映射失败\n");return 0;}/*初始化屏幕*/memset(lcd_p,0xff,finfo.smem_len);while(1){video_decode(FILE_NAME);//视频解码函数}}/*视频解码*/int video_decode(const char *file){ AVFrame *Input_pFrame= NULL;    AVFrame *Output_pFrame = NULL;printf("pth:%s\n",avcodec_configuration());/*获取ffmpeg配置信息*//*初始化所有组件*///av_register_all();/*打开文件*/AVFormatContext *ps=NULL;int res=avformat_open_input(&ps,file,NULL,NULL);if(res!=0){printf("open err: %d\n",res);return 0;}/*寻找解码信息*/avformat_find_stream_info(ps,NULL);int64_t time_s=ps->duration;printf("time:%ld s\n",time_s/1000000);/*打印有关输入或输出格式的详细信息*/av_dump_format(ps,0,file,0);/*寻找视频流信息*/int videostream=-1;int audiostream=-1;videostream=av_find_best_stream(ps,AVMEDIA_TYPE_VIDEO,-1,-1,NULL, 0);printf("video=%d\n",videostream);/*寻找音频流信息*/audiostream=av_find_best_stream(ps,AVMEDIA_TYPE_AUDIO,-1,-1,NULL, 0);printf("audio=%d\n",audiostream);/*寻找视频解码器*/AVStream *stream = ps->streams[videostream];AVCodec *vcodec=avcodec_find_decoder(stream->codecpar->codec_id);if(!vcodec){printf("未找到解码器\n");return -1;}/*申请视频AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。*/res=avcodec_open2(stream->codec,vcodec,NULL);if(res){printf("打开解码器失败\n");return -1;}/*寻找音频解码器*/AVStream *audstream = ps->streams[audiostream];AVCodec *audcodec=avcodec_find_decoder(audstream->codecpar->codec_id);if(!audcodec){printf("audcodec failed\n");return -1;}/*申请音频AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。*/res=avcodec_open2(audstream->codec,audcodec,NULL);if(res){printf("open audio failed\n");return -1;}printf("sucess\n");int sample_rate=audstream->codec->sample_rate;/*采样率*/int channel=audstream->codec->channels;/*通道数*/printf("sample_rate:%d\n",sample_rate);printf("channel:%d\n",channel);int sample_fmt;switch(audstream->codec->sample_fmt){case AV_SAMPLE_FMT_U8:/*8位*/sample_fmt=AV_SAMPLE_FMT_U8;break;case AV_SAMPLE_FMT_FLTP:/*浮点型*/sample_fmt=AV_SAMPLE_FMT_FLTP;break;}int go_audio;AVPacket *packet=av_malloc(sizeof(AVPacket));/*分配包*/AVFrame *frame=av_frame_alloc();/*分配视频帧*/AVFrame *audioframe=av_frame_alloc();/*分配音频帧*/int audiosize=2*1024*2;char *out_buffer = (char*)av_malloc(audiosize);/*对解码数据进行重采样*/SwrContext *swrCtx = swr_alloc();enum AVSampleFormat in_sample_fmt=audstream->codec->sample_fmt;/*输入采样格式*/enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16;/*输出采样格式:16bit PCM*/int in_sample_rate=audstream->codec->sample_rate;/*输入采样率*/int out_sample_rate=44100;/*输出采样率*/uint64_t in_ch_layout=audstream->codecpar->channel_layout;//输入的声道布局uint64_t out_ch_layout=audstream->codecpar->channel_layout;/*立体声*/swr_alloc_set_opts(swrCtx,out_ch_layout,out_sample_fmt,out_sample_rate,/*输入音频格式*/in_ch_layout,in_sample_fmt,in_sample_rate,/*输出音频格式*/0,NULL);swr_init(swrCtx);//视频解码AVFrame *frameRGB=av_frame_alloc();/*申请yuv空间*//*分配空间,进行图像转换*/int width=ps->streams[videostream]->codecpar->width;int height=ps->streams[videostream]->codecpar->height;int fmt=ps->streams[videostream]->codecpar->format;/*流格式*/map_w=800;map_h=480;int size=avpicture_get_size(AV_PIX_FMT_BGR24, map_w,map_h);unsigned char *buff=NULL;printf("w=%d,h=%d,size=%d\n",width,height,size);buff=av_malloc(size);/*计算一帧空间大小*/avpicture_fill((AVPicture *)frameRGB,buff,AV_PIX_FMT_BGR24,map_w,map_h);/*转换上下文*/struct SwsContext *swsctx=sws_getContext(width,height, fmt,map_w,map_h, AV_PIX_FMT_BGR24,SWS_BICUBIC,NULL,NULL,NULL);/*读帧*/int go=0;int Framecount=0;printf("read fream buff\n");int audio_count=0;time_t sec=0,sec2=0;char buff_time[200]={0};struct tm result;while((av_read_frame(ps,packet)>=0)){sec=time(NULL);if(sec!=sec2){sec2=sec;localtime_r(&sec2,&result);//将秒单位时间转换为时间结构体strftime(buff_time,sizeof(buff_time),"%H:%M:%S",&result);//时间格式化打印}if(packet->stream_index == AVMEDIA_TYPE_VIDEO)/*判断是否为视频*/{res=avcodec_decode_video2(ps->streams[videostream]->codec,frame,&go,packet);if(res<0){printf("avcodec_decode_video2 failed\n");return -1;}if(go){sws_scale(swsctx,(const uint8_t **)frame->data,frame->linesize,0,map_h,(const uint8_t **)frameRGB->data,frameRGB->linesize);lcd_p2=buff;NT35310_DisplayStr(lcd_widht/2-strlen(buff_time)/2*16,50,32,buff_time);LCD_Image(map_w,map_h,buff);Framecount++;//printf("frame index:%d\n",Framecount);}}else if(packet->stream_index==AVMEDIA_TYPE_AUDIO)/*音频流*/{res=avcodec_decode_audio4(audstream->codec,audioframe,&go_audio,packet);if(res<0){printf("decode_audio4 failed\n");return -1;}if(go_audio)//音频数据处理{}}}av_free_packet(packet);sws_freeContext(swsctx);av_frame_free(&frame);av_frame_free(&frameRGB);avformat_free_context(ps);return 0;}

  makefile文件

CFLAGS=-I/home/wbyq/tiny4412_pack/ffmpeg-4.2.5/_install/include -L/home/wbyq/tiny4412_pack/ffmpeg-4.2.5/_install/lib CFLAGS+=-I/home/wbyq/tiny4412_pack/x264-master/_install/include -L/home/wbyq/tiny4412_pack/x264-master/_install/libCFLAGS+= -lpthread -lm -ldl -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale -lpthread -lstdc++ -lm -lasound -lx264OBJ=main.c ascii.capp:arm-linux-gcc -o app $(OBJ) $(CFLAGS)   

4.相关函数介绍

4.1 打开输入流并读取头数据avformat_open_input

int avformat_open_input(AVFormatContext ps, const char *filename, ff_const59 AVInputFormat *fmt, AVDictionary options);
函数功能:打开一个输入流并读取头部信息,但编解码器不会打开
   1.分配一个AVFormatContext的实例。
   2.调用init_input函数初始化输入流的信息。这里会初始化AVInputFormat。
   3.根据上一步初始化好的AVInputFormat的类型,调用它的read_header方法,读取文件头。
形 参: AVFormatContext ps 媒体文件或媒体流的构成和基本信息
   filename 输入文件名
   AVInputFormat *fmt 输入文件格式,一般填NULL即可。
     如果fmt参数非空,也就是人为的指定了一个AVInputFormat的实例,那么这个函数就不会再检测输入文件的格式了;如果fmt为空,那么这个函数就会自动探测输入文件的格式等信息。
     AVDictionary options 附加的一些选项,一般填NULL即可;
返回值: 成功返回0

4.2 查找流信息avformat_find_stream_info

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary options);
函数功能:查找流信息
形参: AVFormatContext *ic 输入的流信息
   AVDictionary options 附加的一些选项,一般填NULL即可;
返回值: 成功返回0

4.3 查找输入流中的音视频信息av_find_best_stream

int av_find_best_stream(AVFormatContext *ic,
           enum AVMediaType type,
           int wanted_stream_nb,
            int related_stream,
            AVCodec decoder_ret,
           int flags);

形参: AVFormatContext *ic 输入的流信息
    type 查找类型AVMEDIA_TYPE_VIDEO视频、AVMEDIA_TYPE_AUDIO音频
     wanted_stream_nb 用户请求的流编号,-1用于自动选择,
    related_stream 尝试查找与此相关的流(例如,在同一程序中),如果没有,则为-1
    decoder_ret 如果非空,则返回所选流的解码器,可以填NULL,
     flags 标志,当前未定义任何
返回值: 成功情况下的非负流编号

4.4 初始化音视频编解码器avcodec_open2

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary options);
函数功能:该函数用于 初始化 一个视音频编解码器的AVCodecContext。
形参:avctx 编解码器上下文
   codec 要打开的编解码器
   options 一个包含AVcodeContext和编解码器专用选项的存储工具。
返回值: 成功返回0,失败返回负数

4.5 分配一个重采样swr_alloc_set_opts

struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
           int64_t out_ch_layout,
           enum AVSampleFormat out_sample_fmt,
           int out_sample_rate,
           int64_t in_ch_layout,
           enum AVSampleFormat in_sample_fmt,
           int in_sample_rate,
           int log_offset,
           void *log_ctx);

函数功能:分配一个重采样,设置/重置公共参数
形参: s 现有的Swr上下文(如果可用),或NULL(如果不可用)
    out_ch_layout 输出声道格式
    out_sample_fmt 输出采样频率
    in_ch_layout 输入的声道布局
    in_sample_fmt 输入采样格式
    log_offse、log_ctx 日志信息,填0和NULL即可
返回值: 错误时为NULL,否则为已分配上下文

4.6 sws_getContext

struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                 int dstW, int dstH, enum AVPixelFormat dstFormat,
                 int flags, SwsFilter *srcFilter,
                 SwsFilter *dstFilter, const double *param);

函数功能:分配并返回SwsContext
形参:srcW、 srcH — 源图像的宽度和高度
    srcFormat 源图像格式
    dstW、 dstH 转换后的图像宽度和高度
    dstFormat 转换后的图像格式
    flags 指定用于重新缩放的算法和选项(在swscale.h中有对应宏)
    后面3个参数一般填NULL即可
返回值: 成功执行的话返回生成的SwsContext,否则返回NULL

4.7 解码一帧视频数据avcodec_decode_video2

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                int *got_picture_ptr,
                const AVPacket *avpkt);

函数功能:解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。该函数的声明位于libavcodec\avcodec.h
形参:avctx --编解码上下文
    picture 解码后的结构体,图像信息
    got_picture_ptr该值为0表明没有图像可以解码,否则表明有图像可以解码;
    avpkt 输入参数,包含待解码数据
返回值: 错误时返回负值,否则返回字节数,如果无法解压缩帧,则为零。

4.8 图像转换sws_scale

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
       const int srcStride[], int srcSliceY, int srcSliceH,
       uint8_t *const dst[], const int dstStride[]);

函数功能:视频像素格式和分辨率的转换,可以在同一个函数里实现:
     1.图像色彩空间转换, 2:分辨率缩放,3:前后图像滤波处理。
形参:c — 转换格式的上下文。也就是 sws_getContext 函数返回的结果
    srcSlice[] 输入图像的每个颜色通道的数据指针。其实就是解码后的AVFrame中的data[]数组
    srcStride[] 输入图像的每个颜色通道的跨度。也就是每个通道的行字节数,对应的是解码后的AVFrame中的linesize[]数组
    SrcSliceY、srcSliceH 定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整 个图像,这种设置是为了多线程并行。
    dst[]、dstStride[]定义输出图像信息(输出的每个颜色通道数据指针,每个颜色通道 行字节数)
返回值:成功返回图像高度。