# Android-short-video **Repository Path**: luyan2599/Android-short-video ## Basic Information - **Project Name**: Android-short-video - **Description**: Android-short-video 是又拍云推出的一款适用于 Android 平台的短视频 SDK,它包含短视频拍摄、编辑、合成、上传,还包含短视频播放器,再结合又拍云存储和 CDN,您就可以开启您的短视频业务了。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2020-07-27 - **Last Updated**: 2022-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 又拍云短视频 1. 提供短视频的拍摄、编辑、合成、上传等基础功能。 2. 提供播放器支持。 ## 目录 1 [短视频](#1) 2 [上传](#2) 3 [播放器](#3)

短视频

## 配置环境 ### 1.1 基本介绍 * 短视频拍摄、编辑、合成部分,包含断点录制、分段回删、美颜、大眼、瘦脸、滤镜、贴纸、视频剪辑、视频压缩、本地转码在内的 30 多种功能,支持自定义界面和二次开发。 ### 1.2 运行环境 * Eclipse 或 Android Studio 1.3 + ,Android 系统 4.3 + ### 1.3 密钥 和 资源 * 使用短视频 SDK 需要授权(key)。请提供应用名称、申请使用的 SDK 版本、使用的平台(安卓、iOS)、包名给您的商务经理,或者[联系我们](https://www.upyun.com/contact) 来获取授权。 ### 1.4 环境配置 1、[联系我们](https://www.upyun.com/contact) 获取资源文件,导入至 lib 以及 assert 目录。 ### 1.5 TuSDK 的初始化 1、打开全局 `Application` 文件,全局 `Application` 类可以选择继承 `TuSdkApplication` 或不继承,然后在 `onCreate` 方法中使用 `TuSdk.init()` 方法来进行初始化,并将复制的密钥作为该方法的参数,如下: @Override public void onCreate() { TuSdk.enableDebugLog(true); // 初始化SDK (请将目标项目所对应的密钥放在这里) TuSdk.init(this.getApplicationContext(), "12aa4847a3a9ce68-04-ewdjn1"); } 2、为方便开发时定位错误,可打开 TuSDK 的调试日志,即在初始化密钥之前添加以下代码(放在初始化密钥之后无效): `TuSdk.enableDebugLog(true);` 发布应用时请关闭日志。 ### 1.6 配置 AndroidManifest 在 [`AndroidManifest.xml`](https://github.com/TuSDK/TuSDK-for-Android-demo/blob/master/TuSDKDemo/AndroidManifest.xml) 中,首先定义程序需要的权限: 然后定义应用的全局 `Application` 对象,设置 `allowBackup`、`hardwareAccelerated` 和 `largeHeap` 三个重要选项为 `true`。 ## 录制相机的使用 ### 1.1 请求权限 除了按照集成向导步骤在AndroidManifest.xml加上权限外,在Android 6.0后都需申请动态权限,以下是打开相机所需申请的权限: `String[] permissions = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };` ### 1.2 构建对象 创建并配置 `TuSdkRecorderVideoCamera` 对象,代码如下: RelativeLayout cameraView = (RelativeLayout) findViewById(R.id.lsq_cameraView); // 录制相机配置,目前只支持硬编 TuSdkRecorderCameraSetting captureSetting = new TuSdkRecorderCameraSetting(); TuSdkRecorderVideoCamera videoCamera = new TuSdkRecorderVideoCameraImpl(getBaseContext(),cameraView,captureSetting); 构造方法中需要传入下面三个参数: (1)当前上下文(`Context`) (2)相机采集配置(`TuSdkRecorderCameraSetting`) (3)相机视图容器(`RelativeLayout`) 1.3 相机采集配置 ---------- `TuSDKVideoCaptureSetting` 中可以对相机采集时的多项属性进行设置,包括: * `facing`,相机朝向(默认: `CameraFacing.Front` 前置) * 相机视图容器是一个 `RelativeLayout` 布局,一般在 XML 布局文件中指定,传入 `TuSDKRecordVideoCamera` 构造方法中后,会将相机预览视图加载在该视图容器中。 1.4 相机设置 -------- 获取录制相机对象后,可以对该相机的属性进行一些设置。 * 开启或关闭动态贴纸 // 是否开启动态贴纸(默认: false) public void setEnableLiveSticker(boolean enableLiveStickr) * 开启或关闭自动持续对焦 // 禁用自动持续对焦 (默认: false) public void setDisableContinueFocus(boolean disableContinueFoucs) * 设置水印(图片最大边长不宜超过 500) // 设置水印,默认为空 public void setWaterMarkImage(Bitmap mWaterMarkImage) // 设置水印位置 public void setWaterMarkPosition(WaterMarkPosition mWaterMarkPosition) * 设置视频录制结果委托 public void setRecorderVideoCameraCallback(TuSdkRecorderVideoCameraCallback recorderCallback) 指定录制相机的事件委托,该委托中有下面四个接口用来通知相机状态改变: * 设置最小、最大录制时长(单位:/s) // 设置最小录制时长 public void setMinRecordingTime(int minRecordingTime) // 设置最大录制时长 public void setMaxRecordingTime(int maxRecordingTime) * 设置相机事件委托 使用 `setDelegate(TuSDKVideoCameraDelegate)` 设置相机事件委托,详细可以参看 demo 中的使用示例 * 设置相机拍照监听 public void setCameraListener(TuSdkCameraListener cameraListener) * 设置特效改变监听 public void setMediaEffectChangeListener(TuSdkMediaEffectChangeListener mediaEffectChangeListener) * 设置人脸检测结果回调 public void setFaceDetectionCallback(TuSdkFaceDetectionCallback faceDetectionCallback) * 设置视频编码配置 public final void setVideoEncoderSetting(TuSdkRecorderVideoEncoderSetting videoEncoderSetting) 可以设置视频编码时的参数,包括: `videoSize`,输出视频尺寸(默认:`TuSdkSize(320, 480)`) `videoQuality`,视频质量 `mediacodecAVCIFrameInterval`,I 帧时间间隔 (默认:1) `enableAllKeyFrame` 是否最大限度输出 I 帧,默认false 推荐使用 SDK 默认的编码配置,如下: // 推荐编码配置 TuSdkRecorderVideoEncoderSetting encoderSetting = TuSdkRecorderVideoEncoderSetting.getDefaultRecordSetting(); mVideoCamera.setVideoEncoderSetting(encoderSetting); * 保存系统相册 // 保存系统相册 (默认保存, 当设置为 false 时, 保存为临时文件) public void setSaveToAlbum(boolean mSaveToAlbum) // 保存到系统相册的相册名称 public void setSaveToAlbumName(String mSaveToAlbumName) 当设置 false 可 `TuSdkRecorderVideoCameraCallback` 回调中获取result. * 添加开启人脸检测 public void setEnableFaceDetection(boolean enableFaceDetection) * 是否启用音频录制 public void setEnableAudioCapture(boolean mEnableAudioCapture) ### 1.5 相机操作接口 SDK 提供了多个操作相机的方法,供用户操作相机开启、关闭、恢复、暂停,如下: * 启动相机采集 startCameraCapture() * 停止相机采集 stopCameraCapture() * 恢复相机采集 resumeCameraCapture() * 暂停相机采集 pauseCameraCapture() 同时,这些方法需要配合所在 Activity 的生命周期中使用,如下: @Override protected void onResume() { super.onResume(); resumeCameraCapture(); } @Override protected void onPause() { super.onPause(); pauseCameraCapture(); } @Override protected void onDestroy() { super.onDestroy(); stopCameraCapture(); if (mVideoCamera != null) { mVideoCamera.destroy(); } } ### 1.6 录制接口 录制相机对象可以通过下面四个接口控制开启、暂停和停止录制动作,以及判断是否正在录制中: * 开始录制 startRecording() * 暂停录制 pauseRecording() * 结束录制 stopRecording() * 判断是否正在录制中 isRecording() * 录制结果 配合`setSaveToAlbum`,设置true将视频保存到相册,设置`false`通过`TuSDKVideoResult`获取对应视频的临时文件,可进行自定义操作。 // 设置录制委托 public void setVideoDelegate(TuSDKRecordVideoCameraDelegate mDelegate) /** 录制相机事件委托 */ public static interface TuSdkRecorderVideoCameraCallback { /** * 视频录制结果 * * @param result * 视频结果 */ void onMovieRecordComplete(TuSDKVideoResult result); /** * 录制进度改变 (运行在主线程) * * @param progress 当前录制进度( 0 - 1 ) 相对于mMaxRecordingTime * @param durationTime 当前录制持续时间 单位:/s */ void onMovieRecordProgressChanged(float progress, float durationTime); /** * 录制状态改变 * @param state */ void onMovieRecordStateChanged(RecordState state); /** * 录制出错 * */ void onMovieRecordFailed(RecordError error); } ### 1.7 相机状态 * 相机状态 /** 相机运行状态 */ public enum CameraState { /** 未知状态 */ StateUnknow, /** 正在启动 */ StateStarting, /** 已经启动 */ StateStarted, /** 正在拍摄 */ StateCapturing, /** 拍摄完成 */ StateCaptured } * 录制状态 /** 录制状态 */ public enum RecordState { /** 录制中 */ Recording, /** 正在保存视频 */ Saving, /** 暂停录制 */ Paused, /** 录制完成 */ RecordCompleted, /** 已取消 */ Canceled, } * 错误状态 /** 录制时错误 */ public enum RecordError { /** 未知错误 */ Unknow, /** 可用空间不足 */ NotEnoughSpace, /** 无效的录制时间(录制时间较短无法生成视频) */ InvalidRecordingTime, /** 低于最小录制时间 */ LessMinRecordingTime, /** 超过最大录制时间 */ MoreMaxDuration, /** 保存失败 */ SaveFailed, } ### 1.8 相机监听接口 * 拍摄图片 captureImage() * 可通过`TuSdkCameraListener` 获取拍照结果 /** Video Camera Delegate */ public static interface TuSdkCameraListener { /** * 滤镜更改事件,每次调用 switchFilter 切换滤镜后即触发该事件,运行在主线程 * * @param filter 新的滤镜对象 * @since V3.2.0 */ void onFilterChanged(FilterWrap filter); /** * 相机状态改变 (如需操作UI线程, 请检查当前线程是否为主线程) * * @param camera * 相机对象 * @param newState * 相机运行状态 */ void onVideoCameraStateChanged(TuSdkStillCameraAdapter.CameraState newState); /** * 获取截屏图片 * * @param camera * 相机对象 * @param bitmap * 图片 */ void onVideoCameraScreenShot(Bitmap bitmap); } ### 1.9 RegionHandler 使用说明 使用自定义 `RegionHandler` 可以实现把相机预览视图(非全屏时)上下左右移动指定距离的功能。 可以新建子类继承 `RegionDefaultHandler`,然后子类中重写下面三个方法: void setWrapSize(TuSdkSize size) RectF recalculate(float ratio, TuSdkSize size) RectF changeWithRatio(float ratio, RegionChangerListener listener) 然后使用 `mVideoCamera.setRegionHandler(RegionHandler)` 方法将自己的子类设置进去,最后使用 `mVideoCamera.changeRegionRatio(float)` 方法刷新视图,使 `regionHandler` 生效。 以 1:1 视图为例: // 刷新视图,使 regionHandler 生效, 1:1 视图 mVideoCamera.changeRegionRatio(1.0f); 在 Demo 中是以 1:1 视图显示的,如果要修改成全屏显示,需要修改两个地方: * 删除自定义的 `RegionHandler` * 将 `mVideoCamera.changeRegionRatio(1.0f)` 中的参数修改为 0 ### 1.10 特效改变监听 * 特效数据改变监听 可以根据返回TuSdkMediaEffectData数据类型分类处理。 /** * 特效数据改变 * * @since V3.2.0 **/ public static interface TuSdkMediaEffectChangeListener { /** * 一个新的特效将要被应用 * * @param mediaEffectData 将要应用的特效数据 * @since V3.2.0 */ void didApplyingMediaEffect(TuSdkMediaEffectData mediaEffectData); /** * 特效将要被移除的特效 * * @param mediaEffects * @since V3.2.0 */ void didRemoveMediaEffect(List mediaEffects); } ### 1.11 人脸检测委托 可以返回人脸检测到的`FaceDetectionResultType`和人脸个数。 /** * 人脸检测结果委托 * * @since V3.2.0 */ public enum FaceDetectionResultType { /** * Succeed */ FaceDetected, /** * No face is detected */ NoFaceDetected, } /** * 人脸检测委托 * * @since V3.2.0 */ public interface TuSdkFaceDetectionCallback { /** * 人脸检测结果 * * @param resultType 人脸检测结果类型 * @param faceCount 检测到的人脸数量 * @since V3.2.0 */ void onFaceDetectionResult(FaceDetectionResultType resultType, int faceCount); } 2\. 滤镜使用 (普通滤镜和漫画滤镜) -------------------- ### 2.1 获取滤镜 filterCode 可以在打包下载的资源文件中找到 `lsq_tusdk_configs.json` 文件,之前在控制台所打包的滤镜资源会在这个文件中显示,比如`"name":"lsq_filter_VideoFair"`,则该滤镜的 `filterCode` 即为 `VideoFair`,需注意大小写。 可以将需要用到的 `filterCode` 放到一个滤镜数组中,切换滤镜时从该数组中取出 `filterCode` 即可,如下: // 要支持多款滤镜,直接添加到数组即可 private String[] videoFilters = new String[]{"VideoFair"}; ### 2.2 加载并显示滤镜资源 Demo中滤镜列表使用`FilterRecyclerAdapter`和`RecyclerView`实现 this.mFilterAdapter.setFilterList(Arrays.asList(Constants.VIDEOFILTERS)); 详细实现可以参考短视频 Demo 中的示例。 同时用户也可以使用自己的列表来显示滤镜,并可以自定义滤镜的缩略图(替换掉对应名称的图片即可)。 ### 2.3 切换滤镜效果 * 通过滤镜 Code 使用普通滤镜 mCamera.addMediaEffectData(new TuSdkMediaFilterEffectData(code)); * 通过滤镜 Code 使用动漫滤镜 mCamera.addMediaEffectData(new TuSdkMediaComicEffectData(code)); ### 2.2 滤镜参数调节 通过 `TuSdkMediaEffectChangeListener` 返回的`TuSdkMediaEffectData`进行滤镜参数调节,滤镜调节包含效果`mixied`等,我们提供了专门的参数调节View可进行快捷设置。 mFilterConfigView.setFilterArgs(mediaEffectData,filterArg); // 获取滤镜返回的调节参数 List filterArgs = mediaEffectData.getFilterArgs() // 设置最大值限制范围 默认1.0,smoothing、mixied建议0.7,whitening建议 0.6 filterArg.setMaxValueFactor(factor); // 设置对应的百分比值 filterArg.setPrecentValue(precentValue); // 提交修改后的参数 mediaEffectData.submitFilterParameter(); 3\. 美颜,微整形的使用 ------------- ### 3.1 微整形 设置微整形特效 // 添加一个默认微整形特效 TuSdkMediaPlasticFaceEffect plasticFaceEffect = new TuSdkMediaPlasticFaceEffect(); mCamera.addMediaEffectData(plasticFaceEffect); // 以下为改变默认值进行提交参数 for (SelesParameters.FilterArg arg : plasticFaceEffect.getFilterArgs()) { if (arg.equalsKey("eyeSize")) {// 大眼 arg.setMaxValueFactor(0.85f);// 最大值限制 } if (arg.equalsKey("chinSize")) {// 瘦脸 arg.setMaxValueFactor(0.8f);// 最大值限制 } if (arg.equalsKey("noseSize")) {// 瘦鼻 arg.setMaxValueFactor(0.6f);// 最大值限制 } } for (String key : mDefaultBeautyPercentParams.keySet()) { submitPlasticFaceParamter(key,mDefaultBeautyPercentParams.get(key)); } 详细实现可以参考短视频 Demo 中的示例。 ### 3.2 美肤 设置美肤特效 true 自然(精准)美颜 false 极致美颜 TuSdkMediaSkinFaceEffect skinFaceEffect = new TuSdkMediaSkinFaceEffect(true); // 美白 SelesParameters.FilterArg whiteningArgs = skinFaceEffect.getFilterArg("whitening"); whiteningArgs.setMaxValueFactor(0.6f);//设置最大值限制 // 磨皮 SelesParameters.FilterArg smoothingArgs = skinFaceEffect.getFilterArg("smoothing"); smoothingArgs.setMaxValueFactor(0.7f);//设置最大值限制 whiteningArgs.setPrecentValue(0.3f);//设置默认显示 smoothingArgs.setPrecentValue(0.6f);//设置默认显示 mCamera.addMediaEffectData(skinFaceEffect); 详细实现可以参考短视频 Demo 中的示例。 4\. 道具模块(动态贴纸和哈哈镜) ------------------ Demo中贴纸使用`ViewPager`和`Fragment`进行分类页面展示。 同时用户也可以使用自己的列表来显示贴纸,详细实现可以参考短视频 Demo。 /** * 设置道具适配器 */ public void init(final FragmentManager fm){ // 添加贴纸道具分类数据 mPropsItemCategories.addAll(PropsItemStickerCategory.allCategories()); // 添加哈哈镜道具分类 mPropsItemCategories.addAll(PropsItemMonsterCategory.allCategories()); mPropsItemPagerAdapter = new PropsItemPagerAdapter(fm, new PropsItemPagerAdapter.DataSource() { @Override public Fragment frament(int pageIndex) { PropsItemCategory category = mPropsItemCategories.get(pageIndex); switch (category.getMediaEffectType()) { case TuSdKMediaEffectDataTypeSticker: { StickerPropsItemPageFragment fragment = new StickerPropsItemPageFragment(pageIndex, mPropsItemCategories.get(pageIndex).getItems()); fragment.setItemDelegate(mStickerPropsItemDelegate); return fragment; } default: { PropsItemMonsterPageFragment fragment = new PropsItemMonsterPageFragment(pageIndex, mPropsItemCategories.get(pageIndex).getItems()); fragment.setItemDelegate(mPropsItemDelegate); return fragment; } } } @Override public int pageCount() { return mPropsItemCategories.size(); } }); mPropsItemViewPager.setAdapter(mPropsItemPagerAdapter); mPropsItemTabPagerIndicator.setViewPager(mPropsItemViewPager,0); mPropsItemTabPagerIndicator.setDefaultVisibleCounts(mPropsItemCategories.size()); List itemTitles = new ArrayList<>(); for (PropsItemCategory category : mPropsItemCategories) itemTitles.add(category.getName()); mPropsItemTabPagerIndicator.setTabItems(itemTitles); } ### 4.1 动态贴纸 贴纸道具主要实现部分`StickerPropsItemPageFragment`,请参考Demo实现。 * 贴纸应用和取消 // 应用贴纸 TuSdkMediaStickerEffectData mediaStickerEffectData = new TuSdkMediaStickerEffectData(itemData); mVideoCamera.addMediaEffectData(mediaStickerEffectData); // 移除贴纸 mVideoCamera.removeMediaEffectsWithType(TuSdkMediaEffectData.TuSdkMediaEffectDataType.TuSdKMediaEffectDataTypeSticker); * 贴纸下载 请参考Demo中`StickerRecyclerAdapter`类 通过`TuSDKOnlineStickerDownloader`下载器进行贴纸下载控制 // 初始下载器 mStickerDownloader = new TuSDKOnlineStickerDownloader(); // 设置下载回到 mStickerDownloader.setDelegate(this); // 下载贴纸 public final void downloadStickerGroup(StickerGroup stickerGroup) /** * 下载事件委托 * * @author gh.li * */ public static interface TuSDKOnlineStickerDownloaderDelegate { /** * 下载进度改变 * * @param stickerGroupId * 贴纸分组 * @param progress * 当前进度 * @param status * 状态 */ public void onDownloadProgressChanged(long stickerGroupId, float progress, DownloadTaskStatus status); } * 动态贴纸配置分类 在资源文件夹raw下配置 `customstickercategories.json`,格式如下 { "categories": [ { "categoryName": "搞怪cos", "stickers": [ { "name": "晕", "id": "1622", "previewImage": "/stickerGroup/img?id=1622" }, { "name": "京剧花旦", "id": "1591", "previewImage": "/stickerGroup/img?id=1591" } }] } 动态贴纸资源需是控制台在线资源与打包资源里的动态贴纸,贴纸详情可在点击贴纸图片或者点击查看按钮查询 `categoryName` 组名 `name` 可使用官网预设名称,也可自定义 `id` 使用官网贴纸资源id `previewImage` id=官网贴纸资源id * 获取动态贴纸配置分类文件 贴纸根据集成文档进行上线、打包等操作。 打包过的动态贴纸资源可在贴纸列表里直接使用,在线贴纸如果没有下载过则在列表里显示下载图标,下载至本地后才能使用。 /** * 获取所有贴纸分类 * * @return List */ public static List allCategories() { List categories = new ArrayList<>(); try { InputStream stream = TuSdkContext.context().getResources().openRawResource(R.raw.customstickercategories); if (stream == null) return null; byte buffer[] = new byte[stream.available()]; stream.read(buffer); String json = new String(buffer, "UTF-8"); JSONObject jsonObject = JsonHelper.json(json); JSONArray jsonArray = jsonObject.getJSONArray("categories"); for (int i = 0; i < jsonArray.length(); i++) { JSONObject item = jsonArray.getJSONObject(i); // 该分类下的所有贴纸道具 List propsItems = new ArrayList(); JSONArray jsonArrayGroup = item.getJSONArray("stickers"); for (int j = 0; j < jsonArrayGroup.length(); j++) { JSONObject itemGroup = jsonArrayGroup.getJSONObject(j); StickerGroup group = new StickerGroup(); group.groupId = itemGroup.optLong("id"); group.previewName = itemGroup.optString("previewImage"); group.name = itemGroup.optString("name"); PropsItemSticker propsItem = new PropsItemSticker(group); propsItems.add(propsItem); } // 该贴纸道具分类 PropsItemStickerCategory category = new PropsItemStickerCategory(propsItems); category.setName(item.getString("categoryName")); categories.add(category); } } catch (Exception e) { e.printStackTrace(); } return categories; } 详细实现可以参考短视频 Demo 中的示例。 ### 4.2 哈哈镜 哈哈镜道具主要实现部分`PropsItemMonsterPageFragment`,请参考Demo实现。 * 哈哈镜应用和取消 // 应用哈哈镜 TuSDKMediaMonsterFaceEffect monsterFaceEffect = new TuSDKMediaMonsterFaceEffect(monsterFaceType); mVideoCamera.addMediaEffectData(monsterFaceEffect); // 移除哈哈镜 mVideoCamera.removeMediaEffectsWithType(TuSdkMediaEffectData.TuSdkMediaEffectDataType.TuSdkMediaEffectDataTypeMonsterFace); * 哈哈镜分类 /** * 获取所有哈哈镜分类 * * @return List */ public static List allCategories() { TuSDKMonsterFaceWrap.TuSDKMonsterFaceType[] faceTypes = { TuSDKMonsterFaceTypeBigNose, // 大鼻子 TuSDKMonsterFaceTypePapayaFace, // 木瓜脸 TuSDKMonsterFaceTypePieFace, // 大饼脸 TuSDKMonsterFaceTypeSmallEyes, // 眯眯眼 TuSDKMonsterFaceTypeSnakeFace, // 蛇精脸 TuSDKMonsterFaceTypeSquareFace, // 国字脸 TuSDKMonsterFaceTypeThickLips // 厚嘴唇 }; // 缩略图后缀 String[] faceTypeTitles = { "bignose", "papaya", "pie", "smalleyes", "snake", "square", "thicklips" }; List categories = new ArrayList<>(); List monsters = new ArrayList<>(); for (int i = 0; i sourceList = new ArrayList<>(); for (MovieInfo movieInfo : mVideoPaths) sourceList.add(TuSdkMediaDataSource.create(movieInfo.getPath()).get(0)); /** 准备视频缩略图抽取器 */ final TuSdkVideoImageExtractor imageThumbExtractor = new TuSdkVideoImageExtractor(sourceList); imageThumbExtractor //.setOutputImageSize(TuSdkSize.create(50,50)) // 设置抽取的缩略图大小 .setExtractFrameCount(20) // 设置抽取的图片数量 .setImageListener(new TuSdkVideoImageExtractorListener() { /** * 输出一帧略图信息 * * @param videoImage 视频图片 * @since v3.2.1 */ public void onOutputFrameImage(final TuSdkVideoImageExtractor.VideoImage videoImage) { ThreadHelper.post(new Runnable() { @Override public void run() { mEditorCutView.addBitmap(videoImage.bitmap); if(!isSetDuration) { float duration = mVideoPlayer.durationUs() / 1000000.0f; mEditorCutView.setRangTime(duration); mEditorCutView.setTotalTime(mVideoPlayer.durationUs()); if(duration >0) isSetDuration = true; } mEditorCutView.setMinCutTimeUs(mMinCutTimeUs/(float)mDurationTimeUs); } }); } /** * 抽取器抽取完成 * * @since v3.2.1 */ @Override public void onImageExtractorCompleted(List videoImagesList) { /** 注意: videoImagesList 需要开发者自己释放 bitmap */ imageThumbExtractor.release(); } }) .extractImages(); // 抽取图片 } * 裁剪控制器会对视频本身进行裁剪处理,导出视频并保存到临时文件夹中,用户可自定义处理。 /** * 开始合成视频 */ private void startCompound(){ if (cuter != null) { return; } isCutting = true; List sourceList = new ArrayList<>(); // 遍历视频源 for (MovieInfo movieInfo : mVideoPaths) { sourceList.add(TuSdkMediaDataSource.create(movieInfo.getPath()).get(0)); } // 准备切片时间 TuSdkMediaTimeSlice tuSdkMediaTimeSlice = new TuSdkMediaTimeSlice(mLeftTimeRangUs,mRightTimeRangUs); tuSdkMediaTimeSlice.speed = mVideoPlayer.speed(); // 准备裁剪对象 cuter = new TuSdkMediaFilesCuterImpl(); // 设置裁剪切片时间 cuter.setTimeSlice(tuSdkMediaTimeSlice); // 设置数据源 cuter.setMediaDataSources(sourceList); // 设置文件输出路径 cuter.setOutputFilePath(getOutputTempFilePath().getPath()); // 准备视频格式 MediaFormat videoFormat = TuSdkMediaFormat.buildSafeVideoEncodecFormat( cuter.preferredOutputSize().width, cuter.preferredOutputSize().height, 30, TuSdkVideoQuality.RECORD_MEDIUM2.getBitrate(), MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, 0, 0); // 设置视频输出格式 cuter.setOutputVideoFormat(videoFormat); // 设置音频输出格式 cuter.setOutputAudioFormat(TuSdkMediaFormat.buildSafeAudioEncodecFormat()); // 开始裁剪 cuter.run(new TuSdkMediaProgress() { /** * 裁剪进度回调 * @param progress 进度百分比 0-1 * @param mediaDataSource 当前处理的视频媒体源 * @param index 当前处理的视频索引 * @param total 总共需要处理的文件数 */ @Override public void onProgress(final float progress, TuSdkMediaDataSource mediaDataSource, int index, int total) { ThreadHelper.post(new Runnable() { @Override public void run() { mLoadContent.setVisibility(View.VISIBLE); mLoadProgress.setValue(progress * 100); } }); } /** * 裁剪结束回调 * @param e 如果成功则为Null * @param outputFile 输出文件路径 * @param total 处理文件总数 */ @Override public void onCompleted(Exception e, TuSdkMediaDataSource outputFile, int total) { isCutting = false; ThreadHelper.post(new Runnable() { @Override public void run() { setEnable(true); mLoadContent.setVisibility(View.GONE); mLoadProgress.setValue(0); mPlayBtn.setVisibility(mVideoPlayer.isPause()?View.VISIBLE:View.GONE); } }); Intent intent = new Intent(MovieEditorCutActivity.this,MovieEditorActivity.class); intent.putExtra("videoPath", outputFile.getPath()); startActivity(intent); cuter = null; } }); } * 视频裁剪可以对输出视频设置时间范围、画布裁剪、图像裁剪等,示例如下: // 设置裁剪切片 public void setTimeSlice(TuSdkMediaTimeSlice slice) // 设置数据源 public final void setMediaDataSources(List mediaDataSources) // 设置输出文件路径 public void setOutputFilePath(String filePath) // 设置输出视频格式 public int setOutputVideoFormat(MediaFormat videoFormat) // 设置输出音频格式 public int setOutputAudioFormat(MediaFormat audioFormat) // 进行裁剪 public boolean run(TuSdkMediaProgress progress) 裁剪的回调为 /** 媒体处理进度接口 */ public interface TuSdkMediaProgress { /** * 执行进度 [主线程] * * @param progress 进度百分比 0-1 * @param mediaDataSource 当前处理的视频媒体源 * @param index 当前处理的视频索引 * @param total 总共需要处理的文件数 */ void onProgress(float progress, TuSdkMediaDataSource mediaDataSource, int index, int total); /*** * 完成转码 [主线程] * @param e 如果成功则为Null * @param outputFile 输出文件路径 * @param total 处理文件总数 */ void onCompleted(Exception e, TuSdkMediaDataSource outputFile, int total); } 3.视频编辑 ------ 由`MovieEditorCutActivity` 转码后,进入了视频编辑`MovieEditorActivity`页面, 类名 功能描述 MovieEditorActivity 视频编辑页面 MovieEditorController 视频编辑控制器 TuSdkMovieEditor 视频编辑器 视频编辑的功能是由`TuSdkMovieEditor` 提供,Demo界面以及相关操作,都在`MovieEditorController`里以操作组件的方式封装 * 视频编辑控制器的结构与组成 类名 功能描述 EditorHomeComponent 视频编辑主页面组件 EditorFilterComponent 滤镜效果组件 EditorMVComponent MV效果组件 EditorMusicComponent 配音效果组件 EditorTextComponent 文字效果组件 EditorEffectComponent 特效组件(包括场景特效、时间特效、魔法特效) ### 3.1 视频编辑器的初始化 在`MovieEditorController` 的构造方法中需要初始化视频编辑器,以下是一个最简单的视频加载逻辑。 /** * context 当前context * holderView 视频播放器的父容器 * options 视频配置项 **/ TuSdkMovieEditor mMovieEditor = new TuSdkMovieEditorImpl(context, holderView, options); //之前经历过MovieEditorCutActivity裁剪加载 则不用开启转码 mMovieEditor.setEnableTranscode(false); //加载视频 mMovieEditor.loadVideo(); 接口描述: * 设置转码 /** * 是否开启转码 * * @param isEnableTranscode true 开启 false 不开启 默认开启 */ public void setEnableTranscode(boolean isEnableTranscode); * 加载视频 /** * 加载视频 * * @since 3.0 */ void loadVideo(); * 保存视频 /** * 保存视频 * * @since 3.0 */ void saveVideo(); #### 3.1.1视频编辑配置项中可配置的参数: 简单使用方式如下: TuSdkMovieEditor.TuSdkMovieEditorOptions defaultOptions = TuSdkMovieEditor.TuSdkMovieEditorOptions.defaultOptions(); defaultOptions // 设置视频数据源 .setVideoDataSource(new TuSdkMediaDataSource(mVideoPath)) // 设置是否保存或者播放原音 .setIncludeAudioInVideo(true) // 设置MovieEditor销毁时是否自动清除缓存音频解码信息 .setClearAudioDecodeCacheInfoOnDestory(false) // 设置时间线模式 .setPictureEffectReferTimelineType(TuSdkMediaEffectReferInputTimelineType) // 设置水印 .setWaterImage(BitmapHelper.getBitmapFormRaw(this, R.raw.sample_watermark), TuSdkWaterMarkOption.WaterMarkPosition.TopRight, true); 接口描述: * 设置视频数据源 public TuSdkMovieEditorOptions setVideoDataSource(TuSdkMediaDataSource videoDataSource); * 设置影片保存路径 public TuSdkMovieEditorOptions setMovieOutputFilePath(File movieOutputFilePath); * 设置视频裁剪区域 public TuSdkMovieEditorOptions setCutTimeRange(TuSDKTimeRange cutTimeRange); * 设置画布裁剪区域 public TuSdkMovieEditorOptions setCanvasRectF(RectF canvasRect); * 设置是否保存视频原音 默认 true public TuSdkMovieEditorOptions setIncludeAudioInVideo(boolean includeAudioInVideo); * 设置视频画面特效参照时间线类型 public TuSdkMovieEditorOptions setPictureEffectReferTimelineType(TuSdkMediaPictureEffectReferTimelineType timelineType); * 设置视频输出的宽高 public TuSdkMovieEditorOptions setOutputSize(TuSdkSize outputSize); * 设置是否将视频保存到相册 默认:true public TuSdkMovieEditorOptions setSaveToAlbum(Boolean saveToAlbum); * 保存到系统相册的相册名称 (saveToAlbum 为true时可用) public TuSdkMovieEditorOptions setSaveToAlbumName(String saveToAlbumName); * MovieEditor销毁时是否自动清除音频缓存信息(默认:false 设置为false下次再次使用时可加快载入速度) public TuSdkMovieEditorOptions setClearAudioDecodeCacheInfoOnDestory(boolean clearAudioDecodeCacheInfoOnDestory); * 设置水印图片 /** * waterImage 水印图片 (Bitmap) * watermarkPosition 水印的位置 * isRecycleWaterImage 是否回收水印图片(Bitmap) **/ public TuSdkMovieEditorOptions setWaterImage(Bitmap waterImage, TuSdkWaterMarkOption.WaterMarkPosition watermarkPosition, boolean isRecycleWaterImage) #### 3.1.2 视频编辑的API组成与特效数据类 视频编辑`TuSdkMovieEditor` 中由一下几个组件组成,调用的时候通过一下不同的功能组件调用不同的API 1. `TuSdkEditorTranscoder`转码器 如果没有预转码或者开启了转码,则由此转码器进行视频的裁剪与处理 2. `TuSdkEditorPlayer` 播放器 编辑内的播放器,负责控制时间特效,以及相关播放的API 3. `TuSdkEditorEffector` 特效器 负责特效 添加 删除相关的API 4. `TuSdkEditorAudioMixer`混音器 混音相关的API 5. `TuSdkEditorSaver` 保存器 最后保存视频的相关API 相关特效分为**普通特效**和**时间特效**两种 普通特效: 特效类名 功能描述 TuSDKMediaTextEffectData 文字贴纸特效 TuSDKMediaParticleEffectData 魔法特效 TuSDKMediaStickerAudioEffectData MV特效 TuSDKMediaFilterEffectData 滤镜效果 TuSDKMediaStickerEffectData 贴纸特效 TuSDKMediaAudioEffectData 配音特效 TuSDKMediaSceneEffectData 场景特效 TuSDKMediaComicEffectData 卡通特效 上述特效由`TuSdkEditorEffector` 特效器进行_添加_ 、_删除_的操作,下列三种是时间特效,由`TuSdkEditorPlayer`管理 特效类名 功能描述 TuSDKMediaReversalTimeEffect 倒序时间特效 TuSDKMediaRepeatTimeEffect 反复时间特效 TuSDKMediaSlowTimeEffect 慢动作时间特效 ### 3.2 视频编辑特效的使用 #### 3.2.1 视频编辑滤镜使用 在`EditorFilterComponent`中`mFilterRecyclerView`的`Item`点击回调内,回去到当前点击的滤镜的`code`(通过`mFilterRecyclerAdapter.setFilterList(filterList)`设置) * 滤镜列表,获取滤镜前往 TuSDK.bundle/others/lsq\_tusdk\_configs.json * TuSDK 滤镜信息介绍 @see-https://tusdk.com/docs/ios/self-customize-filter TuSDKMediaFilterEffectData mediaFilterEffectData = new TuSDKMediaFilterEffectData(code); getMovieEditor().getEditorEffector().addMediaEffectData(filterEffectData); 滤镜的改变回调可以通过`TuSdkEditorEffector` 来设置 //设置滤镜改变的回调 getEditorEffector().setFilterChangeListener(mFilterChangeListener); #### 3.2.2 视频编辑MV使用 在`EditorMVComponent`中的`mMvRecyclerView`的`Item`点击回调内 /********************* 添加 *************/ if (mMusicMap != null && mMusicMap.containsKey(groupId)) { //带音效的MV Uri uri = Uri.parse("android.resource://" + getEditorController().getActivity().getPackageName() + "/" + mMusicMap.get(groupId)); //创建MV数据类 TuSDKMediaStickerAudioEffectData stickerAudioEffectDat = new TuSDKMediaStickerAudioEffectData(new TuSdkMediaDataSource(context, uri), itemData); //设置时间范围 stickerAudioEffectDat.setAtTimeRange(TuSDKTimeRange.makeRange(0, Float.MAX_VALUE)); //设置MV的背景音效是否循环播放 stickerAudioEffectDat.getMediaAudioEffectData().getAudioEntry().setLooping(true); //添加MV效果 getEditorEffector().addMediaEffectData(stickerAudioEffectDat); } else { //纯贴纸的MV TuSDKMediaStickerEffectData stickerEffectData = new TuSDKMediaStickerEffectData(itemData); //设置时间范围 stickerEffectData.setAtTimeRange(TuSDKTimeRange.makeRange(0, Float.MAX_VALUE)); //添加MV效果 getMovieEditor().getEditorEffector().addMediaEffectData(stickerEffectData); } #### 3.2.3 视频编辑配乐使用 在`EditorMusicComponent`中的`mMusicRecycle`的`Item`点击中 //创建音频特效对象 TuSDKMediaAudioEffectData audioEffectData = new TuSDKMediaAudioEffectData(new TuSdkMediaDataSource(context, audioPathUri)); //设置时间 audioEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(0, getEditorPlayer().getOutputTotalTimeUS())); 加载音频回调是在混音器中加入 getEditorMixer().addTaskStateListener(mAudioDecoderTask); 回调的状态有 /** * 当前执行类状态 */ public enum State { /** 空闲状态 **/ Idle, /** 正在解码 **/ Decoding, /** 解码完成 **/ Complete, /** 已取消 **/ Cancelled } 音频录音回调 /** * 录音裁剪进度监听 */ public interface OnAudioRecordCuterListener { /** * 当前执行的进度 * * @param percent 当前进度的百分比 (0 ~ 1) * @param currentTimeUS 当前执行的时间(微秒) * @param totalTimeUS 总时长 (微秒) */ void onProgressChanged(float percent, long currentTimeUS, long totalTimeUS); /** * 输出完毕 * * @param outputFile 输出完成的文件 */ void onComplete(File outputFile); } #### 3.2.4 视频编辑文字使用 文字功能在`EditorTextComponent` 中,添加一个文字特效 /** * 将数据转成公用的 TuSDKMediaEffectData * * @param sticker 贴纸数据 * @param bitmap 文字生成的图片 * @param offsetX 相对视频左上角X轴的位置 * @param offsetY 相对视频左上角Y轴的位置 * @param rotation 旋转的角度 * @param startTimeUs 文字特效开始的时间 * @param stopTimeUs 文字特效结束的时间 * @param stickerSize 当前StickerView的宽高(计算比例用) */ //创建一个文字贴纸包装类 TuSDKTextStickerImage stickerImage = new TuSDKTextStickerImage(); //创建文字贴纸数据类 TextStickerData stickerData = new TextStickerData(bitmap, bitmap.getWidth(), bitmap.getHeight(), 0, offsetX, offsetY, rotation); stickerImage.setCurrentSticker(stickerData); //设置设计画布的宽高 stickerImage.setDesignScreenSize(stickerSize); //创建文字特效类 TuSDKMediaTextEffectData mediaTextEffectData = new TuSDKMediaTextEffectData(stickerImage); //设置当前文字特效的时间 mediaTextEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(startTimeUs, stopTimeUs //添加特效 getEditorEffector().addMediaEffectData(textMediaEffectData) #### 3.2.5 视频编辑特效使用 特效里分为**场景特效** 、 **时间特效**、**魔法特效** 三种,在`EditorEffectComponent`中,分为三个`Fragment` 1. `ScreenEffectFragment`场景特效Fragment 2. `TimeEffectFragment` 时间特效Fragment 3. `MagicEffectFragment` 魔法特效Fragment #### 3.2.5.1 场景特效 添加一个场景特效 //创建一个场景特效数据类 TuSDKMediaSceneEffectData mediaSceneEffectData = new TuSDKMediaSceneEffectData(mScreenCode); //设置场景特效的时间 mediaSceneEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(starTimeUs, endTimeUs)); //添加当前场景特效 getEditorEffector().addMediaEffectData(mediaSceneEffectData); 删除一个场景特效 getEditorEffector().removeMediaEffectData(mediaEffectData); #### 3.2.5.2 时间特效 * 反复特效 //实例化反复特效数据类 TuSDKMediaRepeatTimeEffect repeatTimeEffect = new TuSDKMediaRepeatTimeEffect(); //设置开始与结束的时间范围 repeatTimeEffect.setTimeRange(startTimeUS, endTimeUS); //设置反复的次数 repeatTimeEffect.setRepeatCount(2); //是否裁剪多余的时间 repeatTimeEffect.setDropOverTime(false); //应用时间特效 getEditorPlayer().setTimeEffect(repeatTimeEffect); * 慢动作 //实例化慢动作特效数据 TuSDKMediaSlowTimeEffect slowTimeEffect = new TuSDKMediaSlowTimeEffect(); //设置慢动作的时间范围 slowTimeEffect.setTimeRange(startTimeUS, endTimeUS); //设置慢动作的速率 slowTimeEffect.setSpeed(0.6f); //应用时间特效 getEditorPlayer().setTimeEffect(slowTimeEffect); * 时光倒流 //实例化时光倒流特效数据 TuSDKMediaReversalTimeEffect reversalTimeEffect = new TuSDKMediaReversalTimeEffect(); //应用时间特效 getEditorPlayer().setTimeEffect(reversalTimeEffect); * 清除时间特效 getEditorPlayer().clearTimeEffect(); #### 3.2.5.2 魔法特效 * 魔法特效的添加 //实例化魔法效果 TuSDKMediaParticleEffectData effectModel = new TuSDKMediaParticleEffectData(mCurrentMagicCode); //设置粒子大小 effectModel.setSize(mMagicConfig.getSize()); //设置粒子颜色 effectModel.setColor(mMagicConfig.getColor()); //设置粒子位置(持续的移动不断的put 参考Demo中的MagicEffectFragment) effectModel.putPoint(getEditorPlayer().getCurrentTimeUs(), pointF); //预览魔法特效 getEditorEffector().addMediaEffectData(effectModel); ### 3.3 保存视频 保存视频调用`TuSdkMovieEditor.saveVideo()`,保存的回调可以在`saveVieo()` 之前向保存器内添加回调 getEditorSaver().addSaverProgressListener(mSaveProgressListener); 该回调为 //保存进度监听 interface TuSdkSaverProgressListener { /** * 当前进度 * * @param progress * @since v3.0 */ void onProgress(float progress); /** * 保存完成 * * @param outputFile * @since v3.0 */ void onCompleted(TuSdkMediaDataSource outputFile); /** * 保存错误 * * @param e * @since v3.0 */ void onError(Exception e); } ## API 使用示例 1\. 多音轨混合 --------- 类名 功能说明 TuSDKAACAudioFileEncoder AAC音频文件编码器 TuSDKAverageAudioMixer 音频混合器 (参考Demo中`AudioMixedActivity`) * 初始化音频混合对象 TuSDKAverageAudioMixer audioMixer = new TuSDKAverageAudioMixer(); //设置混音回调 audioMixer.setOnAudioMixDelegate(mAudioMixerDelegate); 这里需要设置一个混音的回调`mAudioMixerDelegate` /** * 音频混合Delegate */ private TuSDKAudioMixer.OnAudioMixerDelegate mAudioMixerDelegate = new TuSDKAudioMixer.OnAudioMixerDelegate() { /** * 混合状态改变事件 */ @Override public void onStateChanged(TuSDKAudioMixer.State state) { if (state == TuSDKAudioMixer.State.Complete) { // 停止AAC编码器 mAACFileEncoder.stop(); TuSdk.messageHub().showSuccess(AudioMixedActivity.this, "混合完成"); } else if (state == Decoding || state == Mixing) { TuSdk.messageHub().setStatus(AudioMixedActivity.this, "混合中"); } else if (state == TuSDKAudioMixer.State.Cancelled) { //删除混合的音频文件 delMixedFile(); } } /** * 当前解析到主背景音乐信息时回调该方法,其他音乐将参考该信息进行混合 */ @Override public void onReayTrunkTrackInfo(TuSDKAudioInfo rawInfo) { } @Override public void onMixingError(int errorCode) { TuSdk.messageHub().showError(AudioMixedActivity.this, "混合失败"); } /** * 混合后的音频数据(未经编码) */ @Override public void onMixed(byte[] mixedBytes) { // 编码音频数据 mAACFileEncoder.queueAudio(mixedBytes); } }; 混音的状态是在`TuSDKAudioMixer.State`这个枚举中,有以下几种状态 /** * 混音状态 */ public enum State { /** 空闲状态 */ Idle, /** 正在解码 */ Decoding, /** 解码完成 */ Decoded, /** 混合中 */ Mixing, /** 混合完成 */ Complete, /** 已取消 */ Cancelled } 我们看到在回调中用到了`TuSDKAACAudioFileEncoder`AAC文件编码器 ,AAC文件编码器的初始化方式为 * 初始化AAC音频文件编码器 /** AAC 音频文件编码器,可将混合的音频数据编码为AAC文件 */ TuSDKAACAudioFileEncoder mAACFileEncoder = new TuSDKAACAudioFileEncoder(); // 初始化音频编码器 mAACFileEncoder.initEncoder(TuSDKAudioEncoderSetting.defaultEncoderSetting()); // 设置输入路径 mAACFileEncoder.setOutputFilePath(getMixedAudioPath()); 在初始化调用`initEncoder`的时候,我们会传入一个`TuSDKAudioEncoderSetting`对象,这个是音频编码器的设置,主要用来设置以下的参数 /** TuSDKAudioEncoderSetting **/ /** 录制的音频数据格式 默认:AudioFormat.ENCODING_PCM_16BIT */ public int audioFormat; /** 录制的音频采样率 默认:44100 */ public int sampleRate; /** 录制的音频通道设置 AudioFormat.CHANNEL_IN_MONO 或 AudioFormat.CHANNEL_IN_STEREO 默认:AudioFormat.CHANNEL_IN_STEREO */ public int channelConfig; /** 音频质量 默认:AudioQuality.MEDIUM1 */ public AudioQuality audioQuality; /** 默认:MediaCodecInfo.CodecProfileLevel.AACObjectLC */ public int mediacodecAACProfile; /** 音道数 默认:2 */ public int mediacodecAACChannelCount; /** 是否启用音频缓冲区 */ public boolean enableBuffers; 如果不需要自定义参数的话,直接调用`TuSDKAudioEncoderSetting.defaultEncoderSetting()`获取默认配置即可 * 多音轨开始混合 /** 启动AAC文件编码器 **/ mAACFileEncoder.start(); /** 向音频混合器输入需要混合的音频对象列表 **/ mAudioMixer.mixAudios(getAudioEntryList()); `getAudioEntryList()`为获取音频对象`TuSDKAudioEntry`列表 * 获取音频文件列表 音频对象为`TuSDKAudioEntry.java` 支持`URI`和`Path`两种形式,其中可支持的操作有 /** * 设置改音频是否为主背景 * @param trunk 其他音频混合时将参考该音频 */ public TuSDKAudioEntry setTrunk(boolean trunk); /** * 设置是否循环 * @param looping true : 在背景音乐中循环 */ public TuSDKAudioEntry setLooping(boolean looping); /** * 该音频在主干音频的位置 (mTrunk == true时 忽略该设置) * @param timeRange 时间区间 */ public TuSDKAudioEntry setTimeRange(TuSDKTimeRange timeRange); /** * 设置裁剪区域 * @param cutTimeRange 裁剪时间区间 */ public TuSDKAudioEntry setCutTimeRange(TuSDKTimeRange cutTimeRange) /** * 设置音量 * @param volume (0f - 1f) */ public TuSDKAudioEntry setVolume(float volume); 2\. 音视频混合 --------- 类名 功能说明 TuSDKMP4MovieMixer 对视频和多个音频进行混合 (参考Demo中的`TuSDKMP4MovieMixer`) * 初始化混合器 混合器采用了建造者模式去构建,构建方式如下 TuSDKMP4MovieMixer mMP4MovieMixer = new TuSDKMP4MovieMixer(); mMP4MovieMixer.setDelegate(this) .setOutputFilePath(getMixedVideoPath()) // 设置输出路径 .setVideoSoundVolume(1.0f) // 设置音乐音量 .setClearAudioDecodeCacheInfoOnCompleted(true); // 设置音视频混合完成后是否清除缓存信息 默认:true (false:再次混合时可加快混合速度) ​ 这边会设置一个回调 `OnMP4MovieMixerDelegate` 这个回调内部有这几个方法 /** * 音视频混合Delegate */ public interface OnMP4MovieMixerDelegate { /** * 混合状态改变 * @param state * @see TuSDKMP4MovieMixer.State */ void onStateChanged(TuSDKMP4MovieMixer.State state); /** * 错误状态 * @param code * @see ErrorCode */ void onErrrCode(ErrorCode code); /** * 混合完成 * @param result 视频混合结果 */ void onMixerComplete(TuSDKVideoResult result); } * 开始混合 // mVideoDataSource : 视频路径 mAudioTracks : 待混合的音频数据 true : 是否混合视频原音 mMP4MovieMixer.mix(TuSDKMediaDataSource.create(getVideoPath()), mAudioEntryList, false); ​ 开始混合的时候需要传入 1. **视频的路径`TuSDKMediaDataSource`** 2. **待混合的音频数据`mAudioEntryList`**(`TuSDKAudioEntry`列表,多音轨混合有详细介绍) 3. **是否混合视频原音** 这三个设置 * 状态和错误码 * /** * TuSDKMP4VideoMixer 状态信息 */ public enum State { /** 空闲状态 */ Idle, /** 正在解码 */ Decoding, /** 解码完成 */ Decoded, /** 混合中 */ Mixing, /** 已取消 */ Cancelled, /** 混合视频 */ Failed } /** * 错误码 */ public enum ErrorCode { /** 不支持的视频格式 */ UnsupportedVideoFormat, } 3\. 获取视频缩略视图 ------------ 类名 功能说明 TuSDKVideoImageExtractor 视频帧提取器 (参考Demo中`MovieThumbActivity`) * 视频帧提取器用法 //获取出的每帧图片大小 TuSdkSize tuSdkSize = TuSdkSize.create(TuSdkContext.dip2px(56), TuSdkContext.dip2px(30)); //创建视频帧提取器 TuSDKVideoImageExtractor extractor = TuSDKVideoImageExtractor.createExtractor(); //设置输出图片大小 extractor.setOutputImageSize(tuSdkSize) //设置视频数据源 .setVideoDataSource(TuSDKMediaDataSource.create(getVideoPath())) //设置分离图片的数量 .setExtractFrameCount(15); //设置分离回调,开始提取图片 extractor.asyncExtractImageList(mImageExtractorDelegate); ​ 这里会设置分离的回调,回调的方法为 public interface TuSDKVideoImageExtractorDelegate { /** 此方法是在所有图片分离完毕之后回调 只会回调一次 **/ void onVideoImageListDidLoaded(List images); /** 此方法是在每分离出一张图片就会回调一次 **/ void onVideoNewImageLoaded(Bitmap bitmap); } * 精确提取视频帧 在`TuSDKVideoImageExtractor`中调用此方法可以精确提取视频帧 /** * 获取指定时间的视频缩略图 单位:微秒 * * @param frameTimeUs 侦时间 单位:微妙 * @param quality 质量 0-100 * @return Bitmap */ public Bitmap getFrameAtTime(long frameTimeUs, int quality) 4.多视频拼接 ------- 类名 功能说明 TuSdkMediaSuit.merge() 多视频拼接 * 多视频拼接使用方法 TuSdkMediaSuit.merge(mMoviePathList, muxerPath, ouputVideoFormat, ouputAudioFormat, mediaProgress); 1. `mMoviePathList` 待合成视频列表 2. `muxerPath` 最终生成的文件路径 3. `ouputVideoFormat` 输出的视频格式 4. `ouputAudioFormat`输出的音频格式 5. `mediaProgress`进度回调 进度的回调有以下几个方法 /** 媒体处理进度接口 */ public interface TuSdkMediaProgress { /** * 执行进度 [主线程] * * @param progress 进度百分比 0-1 * @param mediaDataSource 当前处理的视频媒体源 * @param index 当前处理的视频索引 * @param total 总共需要处理的文件数 */ void onProgress(float progress, TuSdkMediaDataSource mediaDataSource, int index, int total); /*** * 完成转码 [主线程] * @param e 如果成功则为Null * @param outputFile 输出文件路径 * @param total 处理文件总数 */ void onCompleted(Exception e, TuSdkMediaDataSource outputFile, int total); } 5.视频时间范围裁剪 ---------- 类名 功能说明 TuSdkMediaSuit.cuter() 视频裁剪 (参考Demo中的 `MovieCutActivity`) * 视频裁剪使用方法 TuSdkMediaSuit.cuter(inputMediaSource, outputFilePath, ouputVideoFormat, ouputAudioFormat, orientation,rectDrawF, rectCutF, timeSlice, mCuterMediaProgress); 1. `inputMediaSource` 输入的视频源 2. `outputFilePath` 输出的地址 3. `ouputVideoFormat` 输出的视频格式信息 4. `ouputAudioFormat` 输入的视频格式信息 5. `orientation` 设置输出视频方向 详见`ImageOrientation` 6. `timeSlice` 设置时间的范围 6.音频录制 ------ 类名 功能说明 TuSDKAudioFileRecorder 音频录制器 (参考Demo中的`AudioRecordActivity`) * 初始化音频录制器 TuSDKAudioFileRecorder mAudioRecorder = new TuSDKAudioFileRecorder(); //设置输出文件类型 mAudioRecorder.setOutputFormat(TuSDKAudioFileRecorder.OutputFormat.AAC); //设置文件录制回调 mAudioRecorder.setAudioRecordDelegate(mRecordAudioDelegate); ​ 文件输出类型可以设置为`PCM`和`AAC`两种文件类型 回调接口有以下几个方法 /** 录音事件回调 */ public static interface TuSDKRecordAudioDelegate { /** * 录制完成 * * @param file */ public void onAudioRecordComplete(File file); /** * 录制状态改变 * * @param state @see RecordState */ void onAudioRecordStateChanged(RecordState state); /** * 错误信息回调 * * @param error @see RecordError */ void onAudioRecordError(RecordError error); } 开始、暂停、继续、结束的方法分别是 //开始录制 mAudioRecorder.start(); //暂停录制 mAudioRecorder.pauseRecord(); //继续录制 mAudioRecorder.resumeRecord(); //结束录制 mAudioRecorder.stop(); 7\. 音频变声 -------- 类名 功能名称 TuSdkAudioPitchEngine 音频变声 ### 1.音频特效的类型 目前音频特效类型有以下几种: //正常 TuSdkSoundPitchType.Normal //怪兽 TuSdkSoundPitchType.Monster //大叔 TuSdkSoundPitchType.Uncle //女生 TuSdkSoundPitchType.Girl //萝莉 TuSdkSoundPitchType.Lolita ### 2.音频特效 API * 将 PCM 数据放入`TuSdkAudioPitchEngine` 队列中 /*** * 处理音频数据 * @param byteBuffer 输入缓存 * @param bufferInfo 缓存信息 * @return 是否已处理 */ void processInputBuffer(ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo); * 通知`TuSdkAudioPitchEngine`输入的 PCM 音频信息变更 /** * 更新音频信息 * @param inputAudioInfo 新的音频信息 */ void changeAudioInfo(TuSdkAudioInfo inputAudioInfo); * 清除缓存并重置音频处理器 /** * 重置处理器 */ void reset(); * 释放当前音频处理器 /** * 释放处理器 */ void release(); ### 3\. 使用示例 // setp1: 初始化输入的音频信息 TuSdkAudioInfo inputAudioInfo = new TuSdkAudioInfo(TuSdkMediaFormat.buildSafeAudioEncodecFormat()); // setp2: 初始化 TuSdkAudioPitchEngine TuSdkAudioPitchEngine audioPitchEngine = new TuSdkAudioPitchEngine(inputAudioInfo); // setp3: 设置 TuSdkAudioPitchEngine 处理回调 audioPitchEngine.setOutputBufferDelegate(mAudioPitchEngineOutputBufferDelegate); // setp4: 设置当前需要的音效 audioPitchEngine.setSoundPitchType(TuSdkAudioPitchEngine.TuSdkSoundPitchType.Normal); // setp5: 在输出PCM数据的地方调用 audioPitchEngine.processInputBuffer(outputByteBuffer, bufferInfo); // setp6: 经过TuSdkAudioPitchEngine处理后的数据在会在回调中返回 /** 音频处理数据输出委托 **/ interface TuSdKAudioEngineOutputBufferDelegate { void onProcess(ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo); } // 在这里添加后续处理逻辑,可用于编码,播放,或写入文件。

又拍云短视频上传

### 使用说明 1.导入上传依赖:compile 'com.upyun:upyun-android-sdk:2.1.0' ### 示例代码 * 表单上传 ``` //空间名 String SPACE = "formtest"; //操作员 String OPERATER = "one"; //密码 String PASSWORD = "***"; //上传路径 String savePath = "/uploads/{year}{mon}{day}/{random32}{.suffix}"; final Map paramsMap = new HashMap<>(); //上传空间 paramsMap.put(Params.BUCKET, SPACE); //保存路径 paramsMap.put(Params.SAVE_KEY, savePath); //添加 CONTENT_LENGTH 参数使用大文件表单上传 paramsMap.put(Params.CONTENT_LENGTH, file.length()); //可选参数(详情见api文档介绍) paramsMap.put(Params.CONTENT_MD5, UpYunUtils.md5Hex(file)); paramsMap.put(Params.RETURN_URL, "httpbin.org/post"); //上传结果回调 UpCompleteListener completeListener = new UpCompleteListener() { @Override public void onComplete(boolean isSuccess, String result) { Log.e(TAG, isSuccess + ":" + result); } }; //进度条回调 UpProgressListener progressListener = new UpProgressListener() { @Override public void onRequestProgress(final long bytesWrite, final long contentLength) { Log.e(TAG, (100 * bytesWrite) / contentLength + "%"); } }; UploadEngine.getInstance().formUpload(file, paramsMap, OPERATER, UpYunUtils.md5(PASSWORD), completeListener, progressListener); ``` * 断点续传 ``` //空间名 String SPACE = "formtest"; //操作员 String OPERATER = "one"; //密码 String PASSWORD = "***"; //上传路径 String path = "/test.mp4"; //初始化断点续传 ResumeUploader uploader = new ResumeUploader(SPACE, OPERATER, UpYunUtils.md5(PASSWORD)); //设置 MD5 校验 uploader.setCheckMD5(true); //设置进度监听 uploader.setOnProgressListener(new UpProgressListener() { @Override public void onRequestProgress(long bytesWrite, long contentLength) { Log.e(TAG, bytesWrite + ":" + contentLength); } }); uploader.upload(file, path, null, new UpCompleteListener() { @Override public void onComplete(boolean isSuccess, String result) { Log.e(TAG, "isSuccess:" + isSuccess + " result:" + result); } }); ```

又拍云播放器

#### 功能说明 * 支持在线视频协议:`HLS`, `RTMP`, `HTTP-FLV` 等 * 支持本地视频播放 * 支持设置窗口大小和全屏设置 * 支持缓冲大小设置 * 提供 UpVideoView 控件 * 支持 ARM, ARMv7a, ARM64v8a, X86 主流芯片体系架构 ### 1.配置环境 #### 1.1 基本介绍 `UPYUN` 播放器 `SDK`。功能完备接口简练,可以快速安装使用, 灵活性强可以满足复杂定制需求。 ### 2.SDK使用说明 #### 2.1 运行环境和兼容性 Android 2.3 (API 9) 及其以上 ### 2.2 安装使用说明 #### 工程使用: 导入 upplayer module 使用:compile project(':upplayer') ### 3.播放器使用 #### 3.1 播放器简单调用 直接使用控件 UpVideoView ```java //设置播放地址 upVideoView.setVideoPath(path); //开始播放 upVideoView.start(); //暂停播放 upVideoView.pause(); //停止释放播放器 upVideoView.release(true); //设置默认缓存区大小 (需在setVideoPath 或者 resume 前执行生效) upVideoView.setBufferSize(int size); //拖拽 upVideoView.seekTo(int msec) ```