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
#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 "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=%dn",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:%sn",avcodec_configuration());/*獲取ffmpeg配置信息*/
/*初始化所有組件*/
//av_register_all();
/*打開文件*/
AVCodecContext *pCodecCtx;//解碼器上下文
AVFormatContext *ps=NULL;//音視頻封裝格式結構體信息
printf("name:%sn",file_name);
int res=avformat_open_input(&ps,file_name,NULL,NULL);
if(res!=0)
{
printf("open err: %dn",res);
return 0;
}
/*尋找解碼信息*/
avformat_find_stream_info(ps,NULL);
int64_t time=ps->duration;
printf("time:%ld sn",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=%dn",videostream);
/*尋找音頻流信息*/
audiostream=av_find_best_stream(ps,AVMEDIA_TYPE_AUDIO,-1,-1,NULL, 0);
printf("audio=%dn",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=%dn",frame_rate);
printf("視頻流ID=%#xn",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 failedn");
return -1;
}
/*申請音頻AVCodecContext空間。需要傳遞一個編碼器,也可以不傳,但不會包含編碼器。*/
pCodecCtx=audstream->codec;//解碼器上下文
res=avcodec_open2(audstream->codec,audcodec,NULL);
if(res)
{
printf("未找到音頻解碼器n");
return -1;
}
printf("音頻流ID=%#xn",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=%dn",pCodecCtx->sample_rate);
out_sample_rate =pCodecCtx->sample_rate;//采樣率,多為44100
/*樣本數量*/
printf("frame_size=%dn",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("聲道格式:%dn",out_channel_layout);
printf("采樣格式:%dn",out_sample_fmt);
printf("樣本數量:%dn",out_nb_samples);
printf("采樣率:%dn",out_sample_rate);
printf("通道個數:%dn",out_channels);
printf("緩沖區大小:%dn",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("輸入音頻格式:%dn",in_channel_layout);
printf("輸入采樣格式:%dn",pCodecCtx->sample_fmt);
printf("輸入采樣率:%dn",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=%dn",fmt);
int size=avpicture_get_size(AV_PIX_FMT_RGB24, width,height);
unsigned char *buff=NULL;
printf("w=%d,h=%d,size=%dn",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 buffn");
//初始化轉碼器
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=%dn",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]->codec,frame);
if(res)
{
av_packet_unref(packet);//釋放這個pkt
continue;
}
sws_scale(swsctx,(const uint8_t **)frame->data,frame->linesize,0,height,(const uint8_t **)frameYUV->data,frameYUV->linesize);
video_pts_time=packet->pts;
//printf("視頻=%ldn",video_pts_time);
SDL_UpdateTexture(sdltext,NULL,buff, width*3);
SDL_RenderCopy(render, sdltext, NULL, NULL); // 拷貝紋理到渲染器
SDL_RenderPresent(render); //渲染
usleep(time);
}
if(packet->stream_index == audiostream) //如果為音頻標志
{
if(audiostream<0)continue;
res=avcodec_send_packet(pCodecCtx,packet);
if(res)
{
printf("avcodec_send_packet failed,res=%dn",res);
av_packet_unref(packet);//釋放這個pkt
continue;
}
res=avcodec_receive_frame(pCodecCtx,audioframe);
if(res)
{
printf("avcodec_receive_frame failed,res=%dn",res);
av_packet_unref(packet);//釋放這個pkt
continue;
}
//數據格式轉換
res=swr_convert(swrCtx,&out_buffer,out_buffer_size,/*重采樣之后的數據*/
(const uint8_t **)audioframe->data,audioframe->nb_samples/*重采樣之前數據*/
);
audio_pts_time=packet->pts;
//printf("音頻:%ldn",audio_pts_time);
if(res>0)
{
//audio_chunk =out_buffer; //指向音頻數據 (PCM data)
//while(audio_len>0){}//等待數據處理完
//audio_len =audioframe->nb_samples;//out_buffer_size;//音頻長度
//audio_pos =out_buffer;//當前播放位置
List_AddNode(list_head,out_buffer,out_buffer_size);//添加節點
}
}
//釋放數據包
av_packet_unref(packet);
}
sws_freeContext(swsctx);
av_frame_free(&frame);
av_frame_free(&frameYUV);
avformat_free_context(ps);
return 0;
}
/*創建鏈表頭*/
struct AUDIO_DATA *List_CreateHead(struct AUDIO_DATA *head)
{
if(head==NULL)
{
head=malloc(sizeof(struct AUDIO_DATA));
head->next=NULL;
}
return head;
}
/*添加節點*/
void List_AddNode(struct AUDIO_DATA *head,unsigned char* audio_buffer,int audio_size)
{
struct AUDIO_DATA *tmp=head;
struct AUDIO_DATA *new_node;
pthread_mutex_lock(&mutex);
/*找到鏈表尾部*/
while(tmp->next)
{
tmp=tmp->next;
}
/*插入新的節點*/
new_node=malloc(sizeof(struct AUDIO_DATA));
new_node->audio_size=audio_size;
new_node->audio_buffer=malloc(audio_size);//分配保存音頻數據大小空間
memcpy(new_node->audio_buffer,audio_buffer,audio_size);
new_node->next=NULL;
/*將新節點接入到鏈表*/
tmp->next=new_node;
pthread_mutex_unlock(&mutex);
}
/*
函數功能:刪除節點
*/
void List_DelNode(struct AUDIO_DATA *head,unsigned char* audio_buffer)
{
struct AUDIO_DATA *tmp=head;
struct AUDIO_DATA *p;
/*找到鏈表中要刪除的節點*/
pthread_mutex_lock(&mutex);
while(tmp->next)
{
p=tmp;
tmp=tmp->next;
if(tmp->audio_buffer==audio_buffer)
{
p->next=tmp->next;
free(tmp->audio_buffer);
free(tmp);
break;
}
}
pthread_mutex_unlock(&mutex);
}
/*
函數功能:遍歷鏈表,得到節點總數量
*/
int List_GetNodeCnt(struct AUDIO_DATA *head)
{
int cnt=0;
struct AUDIO_DATA *tmp=head;
pthread_mutex_lock(&mutex);
while(tmp->next)
{
tmp=tmp->next;
cnt++;
}
pthread_mutex_unlock(&mutex);
return cnt;
}
/*
從鏈表頭取數據
*/
int List_GetNode(struct AUDIO_DATA *head,char *audio_buff,int *audio_size)
{
struct AUDIO_DATA *tmp=head;
struct AUDIO_DATA *ptemp=head;
pthread_mutex_lock(&mutex);
while(tmp->next!=NULL)
{
ptemp=tmp;
tmp=tmp->next;
if(tmp!=NULL)
{
*audio_size=tmp->audio_size;
memcpy(audio_buff,tmp->audio_buffer,tmp->audio_size);
ptemp->next=tmp->next;
free(tmp->audio_buffer);
free(tmp);
pthread_mutex_unlock(&mutex);
return 0;
}
}
pthread_mutex_unlock(&mutex);
return -1;
}
- Makefile文件
OBJ=main.o
CFLAGS=-I/home/wbyq/src_pack/ffmpeg-4.2.5/_install/include -L/home/wbyq/src_pack/ffmpeg-4.2.5/_install/lib
-I/home/wbyq/src_pack/SDL2-2.0.14/_install/include -I/home/wbyq/src_pack/SDL2-2.0.14/_install/include/SDL2 -L/home/wbyq/src_pack/SDL2-2.0.14/_install/lib
-lSDL2 -lpthread -lm -ldl -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale -lpthread -lstdc++ -lm -lasound -lx264
app:$(OBJ)
gcc -o $@ $^ $(CFLAGS)
- 運行示例
-
嵌入式
+關注
關注
5082文章
19123瀏覽量
305151 -
Linux
+關注
關注
87文章
11304瀏覽量
209484 -
嵌入式開發
+關注
關注
18文章
1030瀏覽量
47578 -
音視頻
+關注
關注
4文章
475瀏覽量
29878 -
ffmpeg
+關注
關注
0文章
46瀏覽量
7403
發布評論請先 登錄
相關推薦
評論