思路
基于FFmpeg写一个播放器,其实十分的简单。实际上,主要是对FFmpeg的API的封装,同时,我们需要将音视频通过主机呈现出来,所以还依赖于平台的SDL库,整体步骤和思路如下:
1. 编译用于音视频解码的FFmpeg库;
2. 编译用于音视频呈现的SDL库;
3. 编写主程序完成对音视频的整个调度过程;
编译FFmpeg库
这个步骤在《与FFmpeg的初次邂逅》(404
编译SDL库
这里SDL库我们依赖于1.2.15来进行编译,而不是最新的2.0版本;所以首先到SDL的官方网站http://www.libsdl.org/下载1.2.15版本的源代码进行傻瓜式的编译即可,这里需要注意我们配置的SDL生成库和头文件的位置。
ffmpeg@ubuntu:~/work/test$ tar xzvf SDL-1.2.15.tar.gz
ffmpeg@ubuntu:~/work/test$ cd SDL-1.2.15/
ffmpeg@ubuntu:~/work/test/SDL-1.2.15$ ./configure --prefix=/home/ffmpeg/work/SDL-1.2.15/out
ffmpeg@ubuntu:~/work/test/SDL-1.2.15$ make
编译过程中可能会遇到如下错误,
./src/video/x11/SDL_x11sym.h:168:17: error: conflicting types for ‘_XData32’
SDL_X11_SYM(int,_XData32,(Display *dpy,register long *data,unsigned len),(dpy,data,len),return)
^
./src/video/x11/SDL_x11dyn.c:95:5: note: in definition of macro ‘SDL_X11_SYM’
rc fn params { ret p##fn args ; }
^
In file included from ./src/video/x11/SDL_x11dyn.h:34:0,
from ./src/video/x11/SDL_x11dyn.c:26:
/usr/include/X11/Xlibint.h:568:12: note: previous declaration of ‘_XData32’ was here
extern int _XData32(
^
build-deps:1129: recipe for target 'build/SDL_x11dyn.lo' failed
make: *** [build/SDL_x11dyn.lo] Error 1
ffmpeg@ubuntu:~/work/SDL-1.2.15$
请参考如下方法修改(http://blog.csdn.net/jhting/article/details/38523945),
-SDL_X11_SYM(int,_XData32,(Display *dpy,register long *data,unsigned len),(dpy,data,len),return)
+SDL_X11_SYM(int,_XData32,(Display *dpy,register _Xconst long *data,unsigned len),(dpy,data,len),return)
xplayer播放器
暂且叫我们这个播放器叫xplayer吧,对于代码我们采用Makefile的方式进行管理,其目录结构如下:
下面是整个播放器的Makefile文件:
# xplayer Makefile Sample
# List Compiler Tools
CC = gcc
XX = g++
CFLAGS = -Wall -O -g
# Compile Target
TARGET = xplayer
# Include files
INCDIR = /home/ffmpeg/work/ffmpeg-3.2.4/out/include
INCDIR += /home/ffmpeg/work/ffmpeg-3.2.4
INCDIR += /home/ffmpeg/work/SDL-1.2.15/out/include
INCLUDE = $(foreach dir, $(INCDIR), -I$(dir))
LIBPATH = /home/ffmpeg/work/ffmpeg-3.2.4/out/lib
LIBPATH += /home/ffmpeg/work/SDL-1.2.15/out/lib
LIBSPATH = $(foreach dir, $(LIBPATH), -L$(dir))
# needs to be in linking order
LIB = avfilter avformat avcodec swresample swscale avutil pthread z SDL dl asound
LIBS := $(foreach n,$(LIB),-l$(n))
# Depend on
%.o: %.c
$(CC) $(INCLUDE) -c $< -o $@ $(CFLAGS)
%.o:%.cpp
$(XX) $(INCLUDE) -c $< -o $@ $(CFLAGS)
# Source Code
SOURCES = $(wildcard *.c *.cpp)
# Objs File
OBJS = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES)))
# BIN depend on
$(TARGET) : $(OBJS)
$(XX) -o $(TARGET) $(OBJS) $(LIBS) $(LIBSPATH)
chmod a+x $(TARGET)
# clean
clean :
rm -rf $(OBJS)
rm -rf $(TARGET)
下面是音频相关的audio.c的文件代码:
/*
* Copyright (c) 2017 ericbaba
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "xplayer.h"
static AVFilterContext *in_audio_filter; // the first filter in the audio chain
static AVFilterContext *out_audio_filter; // the last filter in the audio chain
static AVFilterGraph *agraph; // audio filter graph
static struct AudioParams audio_filter_src;
static double audio_diff_cum; /* used for AV difference average computation */
static double audio_diff_avg_coef;
static double audio_diff_threshold;
static int audio_diff_avg_count;
static double audio_clock;
static int audio_buf_size;
static int audio_buf_index;
static int synchronize_audio(short *samples, int samples_size)
{
int n;
double ref_clock;
double diff, avg_diff;
int wanted_size, min_size, max_size;
ref_clock = get_master_clock();
diff = get_audio_clock() - ref_clock;
if(diff < AV_NOSYNC_THRESHOLD)
{
// accumulate the diffs
audio_diff_cum = diff + audio_diff_avg_coef * audio_diff_cum;
if(audio_diff_avg_count < AUDIO_DIFF_AVG_NB)
{
audio_diff_avg_count++;
}
else
{
avg_diff = audio_diff_cum * (1.0 - audio_diff_avg_coef);
if(fabs(avg_diff) >= audio_diff_threshold)
{
n = (2 * global_context.acodec_ctx->channels);
wanted_size = samples_size + ((int)(diff * global_context.acodec_ctx->sample_rate) * n);
min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100);
max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100);
if(wanted_size < min_size)
{
wanted_size = min_size;
}
else if (wanted_size > max_size)
{
wanted_size = max_size;
}
if(wanted_size < samples_size)
{
samples_size = wanted_size;
}
else if(wanted_size > samples_size)
{
uint8_t *samples_end, *q;
int nb;
nb = (samples_size - wanted_size);
samples_end = (uint8_t *)samples + samples_size - n;
q = samples_end + n;
while(nb > 0)
{
memcpy(q, samples_end, n);
q += n;
nb -= n;
}
samples_size = wanted_size;
}
}
}
}
else
{
audio_diff_avg_count = 0;
audio_diff_cum = 0;
}
return samples_size;
}
static int init_filter_graph(AVFilterGraph **graph, AVFilterContext **src, AVFilterContext **sink)
{
AVFilterGraph *filter_graph;
AVFilterContext *abuffer_ctx;
AVFilter *abuffer;
AVFilterContext *aformat_ctx;
AVFilter *aformat;
AVFilterContext *abuffersink_ctx;
AVFilter *abuffersink;
uint8_t options_str[1024];
uint8_t ch_layout[64];
int err;
/* Create a new filtergraph, which will contain all the filters. */
filter_graph = avfilter_graph_alloc();
if (!filter_graph)
{
av_log(NULL, AV_LOG_ERROR, "Unable to create filter graph.\n");
return AVERROR(ENOMEM);
}
/* Create the abuffer filter;
* it will be used for feeding the data into the graph. */
abuffer = avfilter_get_by_name("abuffer");
if (!abuffer)
{
av_log(NULL, AV_LOG_ERROR, "Could not find the abuffer filter.\n");
return AVERROR_FILTER_NOT_FOUND;
}
abuffer_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "src");
if (!abuffer_ctx)
{
av_log(NULL, AV_LOG_ERROR, "Could not allocate the abuffer instance.\n");
return AVERROR(ENOMEM);
}
/* Set the filter options through the AVOptions API. */
av_get_channel_layout_string(ch_layout, sizeof(ch_layout), 0, audio_filter_src.channel_layout);
av_opt