我们从demux开始说起,Chrome中非MSE源都是走的ffmpeg_demuxer,具体实现是在src/media/filters/ffmpeg_demuxer.cc
里面进行的。
ffmpeg_demuxer先借助buffer数据初始化一个format_context,记录视频格式信息,然后调avformat_find_stream_info得到所有的streams,一个stream包含一个track,循环streams,根据codec_id区分audio、video、text三种track,记录每种track的数量,设置播放时长duration,用fist_pts初始化播放开始时间start_time。
同时实例化一个DemuxerStream对象,这个对象会记录视频宽高、是否有旋转角度等,初始化audio_config和video_config,给解码的时候使用。
这里面的每一步几乎都是通过PostTask进行的,即把函数当作一个任务抛给media线程处理,同时传递一个处理完成的回调函数。如果其中有一步挂了就不会进行下一步,例如遇到不支持的容器格式,在第一步初始化就会失败,就不会调回调函数往下走了。
FFmpegVideoDecoder在编译的时候配置了下面几个参数后,编译出来的chrome就支持ffmpeg解码,默认是软解。
如图,FFmpegVideoDecoder的结构很简单,在Chrome的框架下,实现了Decode和onNewFrame函数,Decode是将DecodeBuffer类型的数据送给FFmpegDecodingLoop解码,FFmpegDecodingLoop中会创建AVPacket,获取解码后的AVFrame并且完成AVFrame到chrome的VideoFrame的数据转移,最后通过frame_ready_cb(onnewFrame)回调,将解码的数据送给render。
下面是编译ffmpeg的参数:
is_component_ffmpeg = true
media_use_ffmpeg = true
ffmpeg_branding = "Chrome"
开始这个探索的背景是,在我们的平台上ffmpeg v4l2m2m是可以直接使用的,所以就想在Chrome中也能用v4l2m2m插件走硬解码,这里先忽略硬显示问题以及render和GPU线程在使用hardware decoder的限制,单纯从技术角度探讨下可行性。
通过阅读代码,发现ffmpeg中V4l2m2m默认是没有启用的,在Chrome中ffmpeg的源码是在third_party/ffmpeg/
目录下,但是ffmpeg的编译是由third_party/ffmpeg/BUILD.gn
组织的,在这个文件的开头,首先import了一个ffmpeg_generated.gni
,这个文件也才是ffmpeg编译真正的代码组织文件。
在third_party/ffmpeg/ffmpeg_generated.gni
中,首先会看到NOTE: this file is autogenerated by ffmpeg/chromium/scripts/generate_gn.py
这一行注释,看来这个文件是自动生成的。从生成的代码中发现不同平台编译引用的文件是不完全相同的。
这个以codec_list.c文件来看就可以很清楚。
在third_party/ffmpeg/libavcodec/allcodecs.c中codec_list.c是被include进来的,同时在代码树中可以看到多个codec_list.c文件:
#if CONFIG_OSSFUZZ
const FFCodec * codec_list[] = {
NULL,
NULL,
NULL
};
#else
#include "libavcodec/codec_list.c"
#endif
static AVOnce av_codec_static_init = AV_ONCE_INIT;
static void av_codec_init_static(void)
{
for (int i = 0; codec_list[i]; i++) {
if (codec_list[i]->init_static_data)
codec_list[i]->init_static_data((FFCodec*)codec_list[i]);
}
}
这里列出来linux平台不同ABI对应的文件:
third_party/ffmpeg/chromium/config/Chrome/linux/x64/libavcodec/codec_list.c
third_party/ffmpeg/chromium/config/Chrome/linux/arm/libavcodec/codec_list.c
third_party/ffmpeg/chromium/config/Chrome/linux/ia32/libavcodec/codec_list.c
third_party/ffmpeg/chromium/config/Chrome/linux/arm64/libavcodec/codec_list.c
实际上还有很多,如下,以arm/libavcodec/codec_list.c
为例,默认只有这么几个codec列表:
static const FFCodec * const codec_list[] = {
&ff_h264_decoder,
&ff_theora_decoder,
&ff_vp3_decoder,
&ff_vp8_decoder,
&ff_aac_decoder,
&ff_flac_decoder,
&ff_mp3_decoder,
&ff_vorbis_decoder,
&ff_pcm_alaw_decoder,
&ff_pcm_f32le_decoder,
&ff_pcm_mulaw_decoder,
&ff_pcm_s16be_decoder,
&ff_pcm_s16le_decoder,
&ff_pcm_s24be_decoder,
&ff_pcm_s24le_decoder,
&ff_pcm_s32le_decoder,
&ff_pcm_u8_decoder,
&ff_libopus_decoder,
NULL };
如果只是单纯的调试,只要找对了平台和ABI对应的文件,直接修改这个也是可以的,但是如果是整体编译,这样改肯定不行,交叉编译需要arm和ia32对应的代码一致才可以,这就是后面要说的怎么自动生成这个文件。
回到third_party/ffmpeg/chromium/scripts/build_ffmpeg.py
,如果要重新配置ffmpeg的组件,直接修改build_ffmpeg.py是可以的,当然可以不该,直接传递参数给build_ffmpeg.py是可以的。
先把需要传递的参数写出来:
--enable-v4l2-m2m --enable-swscale \
--enable-decoder=h263_v4l2m2m,h264_v4l2m2m,hevc_v4l2m2m,mpeg1_v4l2m2m,mpeg2_v4l2m2m,vp8_v4l2m2m,vp9_v4l2m2m \
--enable-filter=h264_mp4toannexb,hevc_mp4toannexb \
--enable-demuxer=mpegts,flv,matroska
下面是使用build_ffmpeg.py脚本的过程:
调用ffmpeg的代码如果使用到没有启用的部分,就需要重新生成ffmpeg_generated.gni文件,才能完成编译,最后要生产arm和
86的libffmpeg.so
export PATH=`pwd`/third_party/llvm-build/Release+Asserts/bin:$PATH
./build/linux/sysroot_scripts/install-sysroot.py --arch=arm
cd ./third_party/ffmpeg/
./chromium/scripts/build_ffmpeg.py linux arm-neon --branding Chrome \
-- --enable-v4l2-m2m --enable-swscale \
--enable-decoder=h263_v4l2m2m,h264_v4l2m2m,hevc_v4l2m2m,mpeg1_v4l2m2m,mpeg2_v4l2m2m,vp8_v4l2m2m,vp9_v4l2m2m \
--enable-filter=h264_mp4toannexb,hevc_mp4toannexb \
--enable-demuxer=mpegts,flv,matroska
./chromium/scripts/copy_config.sh
./chromium/scripts/generate_gn.py
cd ../../
export PATH=`pwd`/third_party/llvm-build/Release+Asserts/bin:$PATH
./build/linux/sysroot_scripts/install-sysroot.py --arch=i386
cd ./third_party/ffmpeg/
./chromium/scripts/build_ffmpeg.py linux ia32 --branding Chrome \
-- --enable-v4l2-m2m --enable-swscale \
--enable-decoder=h263_v4l2m2m,h264_v4l2m2m,hevc_v4l2m2m,mpeg1_v4l2m2m,mpeg2_v4l2m2m,vp8_v4l2m2m,vp9_v4l2m2m \
--enable-filter=h264_mp4toannexb,hevc_mp4toannexb \
--enable-demuxer=mpegts,flv,matroska
./chromium/scripts/copy_config.sh
./chromium/scripts/generate_gn.py
cd ../../
export PATH="$PATH:/home/hui/chrome/depot_tools"
gn gen out/default/
autoninja -C out/default/ chrome -v
ld.lld: error: undefined symbol: avcodec_free_context
>>> referenced by ffmpeg_audio_decoder.cc
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-49CB027A656D6EFA991374D541CC87AA4FC1D312:(media::FFmpegAudioDecoder::~FFmpegAudioDecoder())
>>> referenced by ffmpeg_audio_decoder.cc
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-49CB027A656D6EFA991374D541CC87AA4FC1D312:(media::FFmpegAudioDecoder::~FFmpegAudioDecoder())
>>> referenced by ffmpeg_audio_decoder.cc
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-49CB027A656D6EFA991374D541CC87AA4FC1D312:(media::FFmpegAudioDecoder::ConfigureDecoder(media::AudioDecoderConfig const&))
>>> referenced 11 more times
先检查ffmpeg.sigs文件中是否有定义,如果有,删除out/default/clang_x86_v8_arm/obj/third_party/ffmpeg/ffmpeg_internal/
目录下文件,重新编译(autoninja -C out/default/ chrome -v
)。
rm out/default/clang_x86_v8_arm/obj/third_party/ffmpeg/ffmpeg_internal/*
比如添加了swscale库之后:
ld.lld: error: undefined symbol: sws_getContext
>>> referenced by ffmpeg_decoding_loop.cc
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-601B86BFA7603203EE89754C3E35196FBCC0AE0E:(media::FFmpegDecodingLoop::ConvertToI420(AVFrame*, AVFrame*))
ld.lld: error: undefined symbol: sws_scale
>>> referenced by ffmpeg_decoding_loop.cc
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-601B86BFA7603203EE89754C3E35196FBCC0AE0E:(media::FFmpegDecodingLoop::ConvertToI420(AVFrame*, AVFrame*))
ld.lld: error: undefined symbol: sws_freeContext
>>> referenced by ffmpeg_decoding_loop.cc
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-601B86BFA7603203EE89754C3E35196FBCC0AE0E:(media::FFmpegDecodingLoop::ConvertToI420(AVFrame*, AVFrame*))
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
需要更新./third_party/ffmpeg/chromium/ffmpeg.sigs
文件,添加:
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);
int attribute_align_arg sws_scale(struct SwsContext *c,const uint8_t * const srcSlice[],const int srcStride[], int srcSliceY,int srcSliceH, uint8_t *const dst[],const int dstStride[]);
void sws_freeContext(SwsContext *c);
ld.lld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC
>>> defined in clang_x86_v8_arm/thinlto-cache/llvmcache-FE528CDEC76E847EC1A72B262C147253194FC80C
>>> referenced by hscale_fast_bilinear_simd.c
>>> clang_x86_v8_arm/thinlto-cache/llvmcache-FE528CDEC76E847EC1A72B262C147253194FC80C:(ff_init_hscaler_mmxext)
增加–enable-pic重新生成ffmpeg_generated.gni:
# --enable-pic
./build/linux/sysroot_scripts/install-sysroot.py --arch=i386
cd ./third_party/ffmpeg/
##--disable-x86asm
./chromium/scripts/build_ffmpeg.py linux ia32 --branding Chrome \
-- --enable-v4l2-m2m --enable-swscale --disable-x86asm --enable-pic \
--enable-decoder=h263_v4l2m2m,h264_v4l2m2m,hevc_v4l2m2m,mpeg1_v4l2m2m,mpeg2_v4l2m2m,vp8_v4l2m2m,vp9_v4l2m2m \
--enable-filter=h264_mp4toannexb,hevc_mp4toannexb \
--enable-demuxer=mpegts,flv,matroska
./chromium/scripts/copy_config.sh
./chromium/scripts/generate_gn.py
再次编译,验证还是解决不了这个错误。
在./chromium/scripts/build_ffmpeg.py
中增加参数--disable-x86asm
解决不了output.asm找不到的问题,看了下output.asm里面直接是一句include,所以拷贝third_party/ffmpeg/libswscale/x86/autorename_libswscale_x86_output.asm到output.asm编译通过。
# ../../third_party/ffmpeg/libswscale/x86/autorename_libswscale_x86_output.asm:2: \
error: unable to open include file `output.asm': No such file or directory
看了下output.asm文件存在,autorename_libswscale_x86_output.asm直接include了output.asm,不知道为什么会报错,但是直接复制为autorename_libswscale_x86_output.asm,这个文件就解决了:
cp third_party/ffmpeg/libswscale/x86/output.asm \
third_party/ffmpeg/libswscale/x86/autorename_libswscale_x86_output.asm
–disable-x86asm是不行的,前面的fpic可能就是这个引起的,最后总结下,前面这部分是试过无数次之后才得以编译通过,就是要保证这个build_ffmpeg.py后面的参数在不同arch一定要一样,不然碰到的问题是没完没了的。