Linux下基于ffmpeg音视频解码
Posted IT_阿水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux下基于ffmpeg音视频解码相关的知识,希望对你有一定的参考价值。
Linux下基于ffmpeg音视频解码
1.ffmpeg简介
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
Fmpeg 是领先的多媒体框架,能够解码、编码、转码、混合、解密、流媒体、过滤和播放人类和机器创造的几乎所有东西。它支持最晦涩的古老格式,直到最尖端的格式。无论它们是由某个标准委员会、社区还是公司设计的。它还具有高度的便携性。
2.ffmpeg八大库介绍
- avutil工具库
avutil: 工具库(大部分库都需要这个库的支持),如AVLog日志输出、AVOption (AVClass)选项设置、AVDictionary键值对存储、ParseUtil字符串解析等; - avcodec编解码库
avcodec: 编解码(最重要的库) 。其中AVCodec是存储编解码器信息的结构体,包含了解协议,解封装,解码操作;
AVCodec结构体中重点参数说明:
const char *name:编解码器的名字,比较短
const char *long_name:编解码器的名字,全称,比较长
enum AVMediaType type:指明了类型,是视频,音频,还是字幕
enum AVCodecID id:ID,音视频流ID信息,枚举类型
const AVRational *supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
const int *supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小
- avformat: 封装格式处理
AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。
AVIOContext *pb;字节流IO上下文
unsigned int nb_streams;音视频流个数
AVStream **streams;音视频流
char filename[1024];输入输出文件名
int64_t duration;流持续时间,即总时长,单位为us
int bit_rate:比特率(码率)(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据
- avdevice: 各种设备的输入输出
可以读取电脑(或其他设备上)的多媒体设备的数据,或者输出到指定的多媒体设备上。
- avfilter: 滤镜特效处理
libavfilter提供了一个通用的音视频filter框架。使用avfilter可以对音视频数据做一些效果处理如去色调、模糊、水平翻转、裁剪、加方框、叠加文字等功能。
- swscale: 视频像素数据格式转换
libswscale 是 FFmpeg 中完成图像尺寸缩放和像素格式转换的库。用户可以编写程序,调用 libswscale 提供的 API 来进行图像尺寸缩放和像素格式转换。
libswscale中常用函数:
sws_getContext();初始化一个SwsContext。
sws_scale();处理图像数据。
sws_freeContext();释放一个SwsContext。
- swresample: 音频采样数据格式转换
libswresample库功能主要包括高度优化的音频重采样、rematrixing和样本格式转换操作。
- postproc:后加工
(同步、时间计算的简单算法)用于后期效果处理;
3.ffmpeg解码流程
- 打开文件avformat_open_input;
- 寻找解码器avformat_find_stream_info;
- 寻找音视频流信息av_find_best_stream;
- 寻找音视频解码器avcodec_find_decoder;
- 配置音频参数:声道、采样格式、采样率、样本数量、通道个数,进行音频重采样swr_alloc_set_opts(音频格式转码);
- 配置视频解码参数:分配视频帧,申请存放图像数据空间,计算一帧空间大小,进行图像转码sws_getContext;
- 初始化SDL,实现图像渲染和音频播放;
- 读取数据包av_read_frame,实现视频解析和音频解析;
4.ffmpeg解码示例
- 开发环境
开发平台: ubuntu18.04.6
ffmpeg版本: 4.2.5
SDL版本: 2.0.14
- 工程示例
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include <libswresample/swresample.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <stdio.h>
#include <linux/fb.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <poll.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <wchar.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#define FILE_NAME "123.flv"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef enum
false,
true,
bool;
uint8_t *out_buffer;
#define MAX_AUDIO_FRAME_SIZE 1024*100
static Uint8* audio_chunk;
static unsigned int audio_len=0;
static unsigned char *audio_pos;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁
//保存音频数据链表
struct AUDIO_DATA
unsigned char* audio_buffer;
int audio_size;
struct AUDIO_DATA *next;
;
//定义一个链表头
struct AUDIO_DATA *list_head=NULL;
struct AUDIO_DATA *List_CreateHead(struct AUDIO_DATA *head);//创建链表头
void List_AddNode(struct AUDIO_DATA *head,unsigned char* audio_buffer,int audio_size);//添加节点
void List_DelNode(struct AUDIO_DATA *head,unsigned char* audio_buffer);//删除节点
int List_GetNodeCnt(struct AUDIO_DATA *head);//遍历
int List_GetNode(struct AUDIO_DATA *head,char *audio_buff,int *audio_size);
int file_stat=1;
void AudioCallback(void *userdata, Uint8 * stream,int len)
SDL_memset(stream, 0,len);
if(audio_len<=0)
return ;
len=(len>audio_len?audio_len:len);
SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);
audio_pos+=len;
audio_len-=len;
//printf("len=%d\\n",len);
void *Audio_decode(void *arg)
int res;
int audio_size;
char audio_buff[4096*3];
while(1)
res=List_GetNode(list_head,audio_buff,&audio_size);
if(res==0)
audio_chunk = audio_buff; //指向音频数据 (PCM data)
while(audio_len>0)//等待数据处理完
audio_len =audio_size;//音频长度
audio_pos = audio_buff;//当前播放位置
int main(int argc,char *argv[])
if(argc!=2)
printf("格式:./app 文件名\\n");
return 0;
char *file_name=argv[1];
/*SDL初始化*/
SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER);
printf("pth:%s\\n",avcodec_configuration());/*获取ffmpeg配置信息*/
/*初始化所有组件*/
//av_register_all();
/*打开文件*/
AVCodecContext *pCodecCtx;//解码器上下文
AVFormatContext *ps=NULL;//音视频封装格式结构体信息
printf("name:%s\\n",file_name);
int res=avformat_open_input(&ps,file_name,NULL,NULL);
if(res!=0)
printf("open err: %d\\n",res);
return 0;
/*寻找解码信息*/
avformat_find_stream_info(ps,NULL);
int64_t time=ps->duration;
printf("time:%ld s\\n",time/1000000);
/*打印有关输入或输出格式的详细信息*/
av_dump_format(ps,0,file_name,0);
/*寻找视频流信息*/
int videostream=-1;
int audiostream=-1;
AVCodec *vcodec;
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;
int frame_rate;
if(videostream!=-1)//判断是否找到视频流数据
/*寻找视频解码器*/
AVStream *stream = ps->streams[videostream];
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;
frame_rate=stream->avg_frame_rate.num/stream->avg_frame_rate.den;//每秒多少帧
printf("fps=%d\\n",frame_rate);
printf("视频流ID=%#x\\n",vcodec->id);//音频流
/*音频流数据处理*/
AVCodec *audcodec;
AVStream *audstream;
SwrContext *swrCtx;//保存重采样数据,即解码的信息
uint64_t out_channel_layout;//声道
int out_sample_fmt;//采样格式
int out_sample_rate;//采样率
int out_nb_samples;//样本数量
int out_channels;//通道数量
uint64_t in_channel_layout;//输入音频声道
SDL_AudioSpec desired;//SDL音频格式信息
AVFrame *audioframe;//保存音频数据
int out_buffer_size;//音频缓冲区大小
if(audiostream>=0)//判断是否有音频流
/*寻找音频解码器*/
audstream = ps->streams[audiostream];
audcodec=avcodec_find_decoder(audstream->codecpar->codec_id);
if(!audcodec)
printf("audcodec failed\\n");
return -1;
/*申请音频AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。*/
pCodecCtx=audstream->codec;//解码器上下文
res=avcodec_open2(audstream->codec,audcodec,NULL);
if(res)
printf("未找到音频解码器\\n");
return -1;
printf("音频流ID=%#x\\n",audcodec->id);//音频流
printf("配置音频参数\\n");
//输出音频参数
out_channel_layout = AV_CH_LAYOUT_STEREO; //声道格式
out_sample_fmt=AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S32;//;//采样格式
printf("pCodecCtx->sample_rate=%d\\n",pCodecCtx->sample_rate);
out_sample_rate =pCodecCtx->sample_rate;//采样率,多为44100
/*样本数量*/
printf("frame_size=%d\\n",pCodecCtx->frame_size);
if(pCodecCtx->frame_size>0)out_nb_samples=pCodecCtx->frame_size;
else if(audcodec->id == AV_CODEC_ID_AAC) out_nb_samples=1024;/*样本数量nb_samples: AAC-1024 MP3-1152 格式大小 */
else if(audcodec->id == AV_CODEC_ID_MP3)out_nb_samples=1152;
else out_nb_samples=1024;
out_channels=av_get_channel_layout_nb_channels(out_channel_layout);//通道个数
out_buffer_size=av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,out_sample_fmt,1);//获取缓冲区大小
out_buffer=(uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE);
memset(out_buffer,0,out_buffer_size);
printf("声道格式:%d\\n",out_channel_layout);
printf("采样格式:%d\\n",out_sample_fmt);
printf("样本数量:%d\\n",out_nb_samples);
printf("采样率:%d\\n",out_sample_rate);
printf("通道个数:%d\\n",out_channels);
printf("缓冲区大小:%d\\n",out_buffer_size);
//输入音频参数
in_channel_layout=av_get_default_channel_layout(pCodecCtx->channels);//输入声道格式
swrCtx = swr_alloc();
/*对解码数据进行重采样*/
swrCtx=swr_alloc_set_opts(swrCtx,out_channel_layout,out_sample_fmt,out_sample_rate,/*输入音频格式*/
in_channel_layout,pCodecCtx->sample_fmt,pCodecCtx->sample_rate,/*输出音频格式*/
0,NULL);
swr_init(swrCtx);//初始化swrCtx
printf("输入音频格式:%d\\n",in_channel_layout);
printf("输入采样格式:%d\\n",pCodecCtx->sample_fmt);
printf("输入采样率:%d\\n",pCodecCtx->sample_rate);
/*设置音频数据格式*/
desired.freq=out_sample_rate;/*采样率*/
desired.format=AUDIO_S16SYS;/*无符号16位*/
desired.channels=out_channels;/*声道*/
desired.samples=out_nb_samples;/*样本数1024*/
desired.silence=0;/*静音值*/
desired.callback=AudioCallback;
SDL_OpenAudio(&desired,NULL);
SDL_PauseAudio(0);/*开始播放音频,1为播放静音值*/
//分配内存
audioframe=av_frame_alloc();/*分配音频帧*/
printf("音频数据初始化完成");
//视频解码
AVFrame *frame=av_frame_alloc();/*分配视频帧*/
AVFrame *frameYUV=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;/*流格式*/
printf("fmt=%d\\n",fmt);
int size=avpicture_get_size(AV_PIX_FMT_RGB24, width,height);
unsigned char *buff=NULL;
printf("w=%d,h=%d,size=%d\\n",width,height,size);
buff=av_malloc(size);
/*计算一帧空间大小*/
avpicture_fill((AVPicture *)frameYUV,buff,AV_PIX_FMT_RGB24,width,height);
/*转换上下文*/
struct SwsContext *swsctx=sws_getContext(width,height, fmt,width,height, AV_PIX_FMT_RGB24,SWS_BICUBIC,NULL,NULL,NULL);
/*读帧*/
int go=0;
int go_audio;
list_head=List_CreateHead(list_head);//创建链表头
/*创建音频处理线程*/
pthread_t pthid;
pthread_create(&pthid,NULL,Audio_decode,(void *)ps);
pthread_detach(pthid);//设置为分离属性
/*创建窗口*/
SDL_Window *window=SDL_CreateWindow("SDL_VIDEO", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,1280,720,SDL_WINDOW_SHOWN);
/*创建渲染器*/
SDL_Renderer *render=SDL_CreateRenderer(window,-1,SDL_RENDERER_ACCELERATED);
/*清空渲染器*/
SDL_RenderClear(render);
/*创建纹理*/
SDL_Texture *sdltext=SDL_CreateTexture(render,SDL_PIXELFORMAT_RGB24,SDL_TEXTUREACCESS_STREAMING,width,height);
bool quit=true;
SDL_Event event;
printf("read fream buff\\n");
//初始化转码器
AVPacket *packet=av_malloc(sizeof(AVPacket));/*分配包*/
av_init_packet(packet);//初始化
int i=0;
int index=0;
long video_pts_time=0;
long audio_pts_time=0;//音频解码时间
time=(1000000/frame_rate-10000);//时间
printf("time=%d\\n",time);
while((av_read_frame(ps,packet)>=0) && (quit))
SDL_PollEvent(&event);
if(event.type==SDL_QUIT)
quit=false;
continue;
if(packet->stream_index == videostream)/*判断是否为视频*/
res=avcodec_send_packet(ps->streams[videostream]->codec,packet);
if(res)
av_packet_unref(packet);//释放这个pkt
continue;
res=avcodec_receive_frame(ps->streams[videostream]->基于Ubuntu交叉编译FFmpeg Windows SDK
写在前面
FFmpeg是一个开源且跨平台的音视频解决方案,集采集、转码、流式化为一身,项目的libavcodec编解码模块和libavformat媒体格式模块,支持非常非常丰富的编解码格式和容器封装格式,是做媒体相关开发工作必须要掌握和借鉴的一个项目。定制和编译ffmpeg是做流媒体开发迟早要面对的,linux平台上相对简单,windows平台就比较麻烦了。本文的目的就是详细介绍下如何基于ubuntu交叉编译环境,编译和定制ffmpeg。
一 概述
ffmpeg主要是基于linux开发,当然它也支持windows,不过并不支持visual studio系列IDE(因为它使用了大量C99特性,而vs不支持C99),所以要想在windows上使用,必须要通过mingw或cygwin来编译,或者在linux上构建windows交叉编译环境,后者要简单些。这里有个网站http://ffmpeg.zeranoe.com/,作者提供了ffmpeg已经编译好的windows版本,包括静态连接的,动态连接的,以及sdk,基本上隔几天就编译一次。大家可以先上去看一下,如果它编译的符合你的需求(上次看的时候它好像没有加入libfaac,现在不知道了),而你又懒得编译,就可以使用它的。对linux不太熟悉的朋友可能不太想继续往下看,请不要担心,ubuntu的安装和使用非常简单,直接在虚拟机里使用就行。编译的时候,你也只需要使用几个很简单的命令而已。
二 环境准备
1. Ubuntu Linux,本人使用的Ubuntu 11.10,安装在VMware Workstation中
2. MinGW编译环境,我们要非常感谢,zeranoe,它做了一个自动下载和编译ffmpeg所需要的mingw及其他工具链的脚本,请到http://ffmpeg.zeranoe.com/blog/下载最新的“MingGW-w64 Build Script 2.8.2”。解压后新建一个目录把它放进去(本人的目录是/home/haibindev/studio/mingw-w64/),然后打开终端,cd 到MinGW-w64所在目录,执行"chmod +x mingw-w64-build-2.8.2",使这个脚本可以执行。之后运行"./mingw-w64-build-2.8.2",就可以按照提示来做了。如果它提示你缺少依赖库,就按照它的提示先安装依赖库,方式是,执行"sudo apt-get install 库名字"。
3. 设置终端的环境变量
执行一下命令即可。(注意,如果你关闭了终端,或者新建一个终端,需要重新执行一下这个命令。)
export PATH="$PATH:/home/haibindev/studio/mingw-w64/mingw-w64-i686/bin"
三 下载FFmpeg源码和你需要的编解码库的源码
这点不用做什么介绍,先到ffmpeg官网,下载ffmpeg源代码,然后再Google你需要的编解码库,到相关网站下载即可。本人这次编译共下载了以下代码,以做大家参考。
libx264 libfaac libmp3lame libopenjpeg libogg libspeex libvorbis libtheora libxvid ffmpeg
下载完后解压,然后统一放到一个目录中,本人的目录是/home/haibindev/studio/opensource/。下图是全家福(build-ffmpeg是编译ffmpeg的工作目录,下面介绍,freetype我没有编译)。
四 编译
为了方便,我把所有库编译后的结果都存放到了一个统一的目录/home/haibindev/opensdk,方法请见下面的编译命令。
除了第一个命令以外,其余的都是configure命令,执行完之后,还需要执行make命令,然后执行make install。我就不再写出来了。
1. 先执行以下命令
ln -s "/usr/bin/pkg-config" /home/haibindev/studio/mingw-w64/mingw-w64-i686/bin/i686-w64-mingw32-pkg-config
2. libx264
./configure --enable-static --enable-win32thread --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --cross-prefix=i686-w64-mingw32-
3. libfaac
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared --with-mp4v2=no
4. libmp3lame
CFLAGS=-DFORCEINLINE= ./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared --disable-frontend
5. libopenjpeg
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared
6. libogg
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared
7. libspeex
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared --disable-oggtest
8. libvorbis
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared --disable-oggtest
9. libtheora
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --enable-static --disable-shared --disable-oggtest --disable-vorbistest --disable-sdltest --with-ogg-includes=/home/haibindev/opensdk/include --with-ogg-libraries=/home/haibindev/opensdk/lib
10. libxvid
./configure --host=i686-w64-mingw32 --prefix=/home/haibindev/opensdk --disable-pthread
11. ffmpeg
先创建/home/haibindev/studio/opensource/build-ffmpeg,然后cd到该目录,执行
../ffmpeg-0.8.7/configure --disable-static --enable-shared --enable-version3 --enable-gpl --enable-nonfree --disable-pthreads --enable-w32threads --enable-runtime-cpudetect --enable-memalign-hack --enable-libfaac --enable-libmp3lame --enable-libopenjpeg --enable-libspeex --enable-libtheora --enable-libvorbis --enable-libx264 --enable-libxvid --enable-zlib --enable-cross-compile --target-os=mingw32 --arch=x86 --prefix=/home/haibindev/opensdk/ffmpeg --cross-prefix=i686-w64-mingw32- --extra-cflags="-I/home/haibindev/opensdk/include" --extra-ldflags="-L/home/haibindev/opensdk/lib"
注意,编译xvid时需要先编辑configure文件,然后删除里面的“-mno-cygwin”字段,否则make会失败,提示-mno-cygwin无效。libmp3lame命中中最前面的“CFLAGS=-DFORCEINLINE= ”是为了防止mp3lame中的一个编译错误,要记得带上。
以上编译成功之后,ffmpeg win32 sdk就出现在/home/haibindev/opensdk/ffmpeg里面了,bin目录下就是我们需要的所有文件了。
五 验证编译后的程序
拷贝到windows上,在命令中运行ffmpeg.exe,看
验证一下,做个转码操作就行了,执行
ffmpeg.exe -i s.avi -vcodec libx264 -acodec libfaac s.mp4
运行正常,生成了264编码和aac编码的,mp4文件,播放正常,ok了。
六 最后说一下
请大家编译的时候千万别忘了configure之后,要执行make和make install!另外,一定要看清楚我贴出来的命令,把我的目录,换成你的目录,不要直接拷贝,那是不行的,因为你不叫haibindev:)。如果有的库在make的时候出现了编译错误,简单修改一下源文件就能搞定。
ffmpeg有很多配置选项,你可以对它深度定制,可以把avcodec编译得很小,大家可以自己研究。
以上是关于Linux下基于ffmpeg音视频解码的主要内容,如果未能解决你的问题,请参考以下文章