不知不觉已经到了2019年,本系列的文章也更新到了8篇。很庆幸笔者能坚持下来,从我司的代码中学习到了很多东西。当然更庆幸的是收获了众多读者的鼓励和支持。从本篇文章开始,我们将接触短视频 app 中比较核心的功能——视频编辑,笔者在我司的日常工作中,也经常对这个模块进行开发,可以说对这部分功能比较熟悉了。所以最近的几篇文章,我会从零开始完善一个视频编辑 sdk 的各种功能,最后集成到我们之前的 MyTiktok 项目中。注:本文以 android 平台为例子,ios 因为不会,所以暂时不涉及。
本文分为以下章节,读者可按需阅读:
我想看本文的人有很大一部分都是 android 工程师,所以在讲干货之前,我需要讲一讲方法论
那么废话不多说,就开始搭建我们的项目吧。注意:目前 MyTiktokVideoEditor 已经上传到了 github 上面了,建议结合项目食用,
图1:根目录
图2:mttvideoeditorsdk目录
图3:mttvideoeditorsdk的gradle文件
上面讲了如何搭建项目,这一章就来讲讲如何集成一些基础库吧。
首先我们都知道,在 android 中我们可以使用 gradle 向远程中央仓库拉取我们需要的库。像 java 的 maven、js 的 npm、ios 的 pods都有这个能力。但是在 c/c++ 上的项目管理工具 CMake 就没有这个能力,它只能在本地搜索和集成你已经安装好的库或者源码,而且 c/c++ 又不具有跨平台能力。所以最终就导致了我们如果想使用 ffmpeg、protobuf 这样大型的开源项目都需要自己去 clone 源码然后自己编译出不同平台的库。
图4:android_ffmpeg目录
----代码块1,本文发自简书、掘金:何时夕-----
cmake_minimum_required(VERSION 3.4.1)
# 当前文件存在的目录
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
# MyTiktokVideoEditor 的根目录
set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../..)
# ffmpeg 的目录
set(FFMPEG_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../android_ffmpeg)
# protobuf 头文件与静态库的目录
set(PROTOBUF_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../android_protobuf)
# android 专用 c++ 代码的目录
set(EDITORSDK_JNI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/editorsdk)
# c++ 共享代码的目录
set(SHARED_CODE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../sharedcode)
# c++ 的版本
set(CMAKE_CXX_STANDARD 11)
# 找到 android ndk 的 log 库
find_library(log-lib log)
# 将 libffmpeg.so 添加到 libffmpeg 这个 动态 library中
add_library(libffmpeg SHARED IMPORTED)
set_target_properties(libffmpeg PROPERTIES IMPORTED_LOCATION
${FFMPEG_LIB_DIR}/armeabi/libffmpeg.so)
# 将 libprotobuf.a 添加到 libprotobuf-lite 这个 静态 library 中
add_library(libprotobuf-lite STATIC IMPORTED)
set_target_properties(libprotobuf-lite PROPERTIES IMPORTED_LOCATION
${PROTOBUF_LIB_DIR}/armeabi/libprotobuf-lite.a)
aux_source_directory(${SOURCE_DIR} SOURCE_DIR_ROOT)
# 将所有自己写的 c++ 代码添加到 mttvideoeditorsdkjni 这个 动态 library 中
list(APPEND SOURCE_DIR_ROOT
${EDITORSDK_JNI_DIR}/native-lib.cc
${EDITORSDK_JNI_DIR}/ffmpeg_sample_six.cpp)
list(APPEND SOURCE_DIR_ROOT
${SHARED_CODE_DIR}/editorsdk/base/av_utils.cc
${SHARED_CODE_DIR}/editorsdk/generated_protobuf/editor_model.pb.cc)
add_library(mttvideoeditorsdkjni
SHARED
${SOURCE_DIR_ROOT})
# 将所有头文件添加到一个列表中,在最后一起链接
list(APPEND SOURCE_DIR_INCLUDE
${SHARED_CODE_DIR}/editorsdk/base/av_utils.h
${SHARED_CODE_DIR}/editorsdk/base/blocking_queue.h
${SHARED_CODE_DIR}/editorsdk/generated_protobuf/editor_model.pb.h
${PROTOBUF_LIB_DIR}/include # 将 protobuf 的头文件放入一个列表中
${FFMPEG_LIB_DIR}/include) # 将 ffmpeg 的头文件放入一个列表中
target_include_directories(mttvideoeditorsdkjni PRIVATE ${SOURCE_DIR_INCLUDE}) # 连接列表中所有的头文件
list(APPEND LINK_LIBRARIES
mttvideoeditorsdkjni
-landroid
libprotobuf-lite
libffmpeg) # 将所有的库添加到一个列表中,最后一起链接
target_compile_options(mttvideoeditorsdkjni PUBLIC -D_LIBCPP_HAS_THREAD_SAFETY_ANNOTATIONS -Wthread-safety -Werror -Wall -Wno-documentation -Wno-shorten-64-to-32 -Wno-nullability-completeness)
target_link_libraries(${LINK_LIBRARIES} ${log-lib}) # 链接所有库
----代码块2,本文发自简书、掘金:何时夕-----
#!/bin/bash
show_msg() {
echo -e "\033[36m$1\033[0m"
}
show_err() {
echo -e "\033[31m$1\033[0m"
}
# protobuf 的版本
v3_0_0="v3.0.0"
# 当前的目录
script_path=$(cd `dirname $0`; pwd)
# protoc 是 protobuf 编译之后生成的可执行文件,可以用来根据 proto 文件生成 java、c++等等代码
protoc_path=$script_path/tools/protoc
# protobuf 的源码地址
protoc_src=$script_path/protobuf
# 生成的 java 文件需要移动到的位置
java_target_path="$script_path/../android/mttvideoeditorsdk/src/main"
# 生成的 c++ 文件需要移动的位置
cpp_target_path="$script_path/../sharedcode/editorsdk/generated_protobuf"
# 本方法用于执行 protobuf 源码的脚本进行编译
build_protobuf() {
mkdir -p $protoc_src/host
mkdir -p $protoc_path/$1
cd $protoc_src/host
../configure --prefix=$protoc_path/$1 && make -j8 && make install
if test $? != 0; then
show_err "Build protobuf failed"
exit 1
fi
cd $script_path
rm -rf $protoc_src/host
}
# 本方法用于 clone protobuf 的源码,然后 checkout 到3.0.0的版本,然后调用 build_protobuf 进行编译
build() {
git clone https://github.com/google/protobuf.git
show_msg "Building android protobuff source code"
cd protobuf
git checkout $v3_0_0
git cherry-pick bba446b # fix issue https://github.com/google/protobuf/issues/2063
./autogen.sh
build_protobuf $v3_0_0
show_msg "Build protobuf complete"
cd $script_path
rm -rf protobuf
}
# 如果 protoc 不存在,那么就去 clone protobuf 的源码,然后编译
if [ ! -x "$protoc_path/$v3_0_0/bin/protoc" ]; then
build
fi
# 删除之前已经生成的 java c++ 文件
rm $java_target_path/java/com/whensunset/mttvideoeditorsdk/model/protobuf/*.java
rm $cpp_target_path/*.pb.cc $cpp_target_path/*.pb.h
cd $script_path/../sharedproto
mkdir -p java cpp
# 用 protoc 生成 java c++ 文件
$protoc_path/$v3_0_0/bin/protoc *.proto --java_out=java --cpp_out=cpp
# 将生成的 java c++ 文件移动到对应的文件夹下
cp -r java $java_target_path
mkdir -p $cpp_target_path
cp cpp/* $cpp_target_path
rm -rf java cpp
图5:android_protobuf目录
最后一章我们来定义一下在一个视频编辑过程中,需要用到的数据结构。
syntax = "proto3";
package sharedcode;
option optimize_for = LITE_RUNTIME;
option java_package = "com.whensunset.mttvideoeditorsdk.model.protobuf";
// 用于保存一段时间,单位是秒
message TimeRange {
double start = 1;
double duration = 2;
uint64 id = 3;
}
// 一个多媒体文件的一个多媒体数据流的信息
message MediaStreamHolder {
// 视频的长和宽
int32 width = 1;
int32 height = 2;
// 编解码器的名称
string codec_type = 3;
// 视频的旋转角度
int32 rotation = 4;
// 视频像素的格式
int32 pix_format = 5;
// 视频的色彩空间,rgb、yuv 等等
int32 color_space = 6;
// 视频的色彩范围
int32 color_range = 7;
// 视频的 bit 流
int64 bit_rate = 8;
}
// 储存一个多媒体文件的信息,减少反复解析的性能消耗
message FileHolder {
string path = 1;
// 文件的后缀名
string format_name = 2;
int32 probe_score = 3;
// 文件中的多媒体数据流的数量
int32 num_streams = 4;
// 文件中的多媒体数据流的信息列表
repeated MediaStreamHolder streams = 5;
// 文件中多媒体信息流中最优的视频流
int32 video_strema_index = 6;
// 文件中多媒体信息流中最优的音频流
int32 audio_strema_index = 7;
string video_comment = 8;
}
message Color {
float red = 1;
float green = 2;
float blue = 3;
float alpha = 4;
}
// 素材的种类
enum AssetType {
ASSET_TYPE_VIDEO = 0;
ASSET_TYPE_AUDIO = 1;
}
// 表示一个视频素材
message VideoAsset {
// 相同表示当前素材是同样的
uint64 asset_id = 1;
string asset_path = 2;
FileHolder asset_video_file_hodler = 3;
// 当前素材被剪裁的时间区域
repeated TimeRange clipped_time_range = 4;
// 视频的速度
double speed = 5;
// 视频声音大小
double volume = 6;
bool is_reversed = 7;
}
// 表示一个音频的素材
message AudioAsset {
uint64 asset_id = 1;
string asset_path = 2;
FileHolder asset_audio_file_holder = 3;
repeated TimeRange clipped_time_range = 4;
double speed = 5;
double volume = 6;
bool is_repeat = 7;
}
// 表示一次视频编辑的流程
message VideoWorkspace {
int64 work_space_id = 1;
repeated VideoAsset video_asset = 2;
repeated AudioAsset audio_asset = 3;
repeated TimeRange clipped_ranges = 4;
int32 workspace_output_width = 5;
int32 workspace_output_height = 6;
VideoEncoderType video_encoder_type = 7;
}
// 当前视频编辑流程使用的编解码方式
enum VideoEncoderType {
VIDEO_ENCODER_TYPE_FFMPEG_MJPEG = 0;
VIDEO_ENCODER_TYPE_MEDIACODEC = 1;
}
不知不觉又水了一篇文章_,最近的两篇文章都是代码多而文字少。不知道大家是不是喜欢这种方式呢?(感觉以前废话太多了,哈哈)大家有什么建议或者意见希望能在评论区提出来。如果文章问题可以指出是哪里,方便我进行修改(手动@上篇文章中说我文章有错别字的哥们)。最近点赞关注有点少啊,希望大家看完能随手点个赞和关注,谢谢啦!