HarmonyOS实现音视频分离合成和截取

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://harmonyos.51cto.com​

音视频分离合理直播分享

权限配置:

"reqPermissions": [
{"name": "ohos.permission.WRITE_MEDIA"},
{"name": "ohos.permission.READ_MEDIA"}
]

requestPermissionsFromUser(
new String[]{SystemPermission.WRITE_MEDIA
,SystemPermission.READ_MEDIA},0);

界面布局:

主页:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="vertical">

<Button
ohos:id="$+id:go_separate_btn"
ohos:text="音视频分离和合成"
ohos:width="200vp"
ohos:height="match_content"
ohos:padding="8vp"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="20fp"/>

<Button
ohos:id="$+id:go_cut_btn"
ohos:text="视频剪切"
ohos:width="200vp"
ohos:height="match_content"
ohos:padding="8vp"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:top_margin="15vp"
ohos:text_size="20fp"/>

<Button
ohos:id="$+id:go_join_btn"
ohos:text="视频拼接"
ohos:width="200vp"
ohos:height="match_content"
ohos:padding="8vp"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:top_margin="15vp"
ohos:text_size="20fp"/>

</DirectionalLayout>

分离合成分离性:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="start"
ohos:orientation="vertical">

<Text
ohos:height="match_content"
ohos:width="match_parent"
ohos:text_size="15fp"
ohos:text_color="#000"
ohos:text="提供音频数据:"
ohos:text_alignment="left"
/>

<DirectionalLayout
ohos:id="$+id:play_directionalLayout1"
ohos:width="match_parent"
ohos:height="240vp"
ohos:alignment="center"
ohos:background_element="#000000"
/>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:alignment="vertical_center"
ohos:orientation="horizontal"
>

<Text
ohos:id="$+id:current_time1"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="00:00:00"
ohos:text_color="#000"
ohos:text_size="12vp"/>

<Slider
ohos:id="$+id:progress1"
ohos:height="35vp"
ohos:width="0vp"
ohos:start_margin="5vp"
ohos:end_margin="5vp"
ohos:orientation="horizontal"
ohos:progress_element="#FF6103"
ohos:progress_width="5vp"
ohos:weight="1"/>

<Text
ohos:id="$+id:end_time1"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="00:00:00"
ohos:text_color="#000"
ohos:text_size="12vp"/>
</DirectionalLayout>

<DirectionalLayout
ohos:width="match_parent"
ohos:height="match_content"
ohos:top_margin="0vp"
ohos:left_margin="20vp"
ohos:right_margin="20vp"
ohos:layout_alignment="horizontal_center"
ohos:orientation="horizontal">

<Button
ohos:id="$+id:start_time_btn1"
ohos:text="确定开始时间"
ohos:width="0vp"
ohos:weight="1"
ohos:height="match_content"
ohos:padding="5vp"
ohos:right_margin="10vp"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="18fp"/>

<Button
ohos:id="$+id:end_time_btn1"
ohos:text="确定结束时间"
ohos:width="0vp"
ohos:weight="1"
ohos:height="match_content"
ohos:padding="5vp"
ohos:left_margin="10vp"
ohos:layout_alignment="vertical_center"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="18fp"/>
</DirectionalLayout>
<Text
ohos:height="match_content"
ohos:width="match_parent"
ohos:text_size="15fp"
ohos:text_color="#000"
ohos:text="提供视频数据:"
ohos:text_alignment="left"
ohos:top_margin="10vp"
/>

<DirectionalLayout
ohos:id="$+id:play_directionalLayout2"
ohos:width="match_parent"
ohos:height="240vp"
ohos:alignment="center"
ohos:background_element="#000000"
/>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:alignment="vertical_center"
ohos:orientation="horizontal"
>
<Text
ohos:id="$+id:current_time2"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="00:00:00"
ohos:text_color="#000"
ohos:text_size="12vp"/>

<Slider
ohos:id="$+id:progress2"
ohos:height="35vp"
ohos:width="0vp"
ohos:start_margin="5vp"
ohos:end_margin="5vp"
ohos:orientation="horizontal"
ohos:progress_element="#FF6103"
ohos:progress_width="5vp"
ohos:weight="1"/>

<Text
ohos:id="$+id:end_time2"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="00:00:00"
ohos:text_color="#000"
ohos:text_size="12vp"/>
</DirectionalLayout>
<DirectionalLayout
ohos:width="match_parent"
ohos:height="match_content"
ohos:top_margin="0vp"
ohos:left_margin="20vp"
ohos:right_margin="20vp"
ohos:layout_alignment="horizontal_center"
ohos:orientation="horizontal">

<Button
ohos:id="$+id:start_time_btn2"
ohos:text="确定开始时间"
ohos:width="0vp"
ohos:weight="1"
ohos:height="match_content"
ohos:padding="5vp"
ohos:right_margin="10vp"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="18fp"/>
<Button
ohos:id="$+id:end_time_btn2"
ohos:text="确定结束时间"
ohos:width="0vp"
ohos:weight="1"
ohos:height="match_content"
ohos:padding="5vp"
ohos:left_margin="10vp"
ohos:layout_alignment="vertical_center"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="18fp"/>
</DirectionalLayout>
<Button
ohos:id="$+id:separate_btn"
ohos:height="match_content"
ohos:width="match_content"
ohos:padding="5vp"
ohos:text="开始分离合成"
ohos:text_size="18fp"
ohos:background_element="#ff0000"
ohos:top_margin="15vp"
ohos:layout_alignment="center"
/>
</DirectionalLayout>

视频剪切:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<Text
ohos:height="match_content"
ohos:width="match_parent"
ohos:text_size="20fp"
ohos:text_color="#000"
ohos:text="原视频"
ohos:text_alignment="center"
/>

<DirectionalLayout
ohos:id="$+id:cut_play_directionalLayout"
ohos:width="match_parent"
ohos:height="240vp"
ohos:alignment="center"
ohos:background_element="#000000"
/>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:alignment="vertical_center"
ohos:orientation="horizontal"
>
<Text
ohos:id="$+id:current_time"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="00:00:00"
ohos:text_color="#000"
ohos:text_size="12vp"/>
<Slider
ohos:id="$+id:progress"
ohos:height="35vp"
ohos:width="0vp"
ohos:start_margin="5vp"
ohos:end_margin="5vp"
ohos:orientation="horizontal"
ohos:progress_element="#FF6103"
ohos:progress_width="5vp"
ohos:weight="1"/>
<Text
ohos:id="$+id:end_time"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="00:00:00"
ohos:text_color="#000"
ohos:text_size="12vp"/>
</DirectionalLayout>
<DirectionalLayout
ohos:width="match_parent"
ohos:height="match_content"
ohos:top_margin="0vp"
ohos:left_margin="20vp"
ohos:right_margin="20vp"
ohos:layout_alignment="horizontal_center"
ohos:orientation="horizontal">
<Button
ohos:id="$+id:start_time_btn"
ohos:text="确定开始时间"
ohos:width="0vp"
ohos:weight="1"
ohos:height="match_content"
ohos:padding="8vp"
ohos:right_margin="10vp"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="20fp"/>
<Button
ohos:id="$+id:end_time_btn"
ohos:text="确定结束时间"
ohos:width="0vp"
ohos:weight="1"
ohos:height="match_content"
ohos:padding="8vp"
ohos:left_margin="10vp"
ohos:layout_alignment="vertical_center"
ohos:background_element="blue"
ohos:text_color="#fff"
ohos:text_size="20fp"/>
</DirectionalLayout>
<Button
ohos:id="$+id:cut_btn"
ohos:height="match_content"
ohos:width="match_content"
ohos:padding="8vp"
ohos:text="开始剪切"
ohos:text_size="22fp"
ohos:background_element="#ff0000"
ohos:top_margin="15vp"
ohos:layout_alignment="center"
/>
</DirectionalLayout>

工具类:

读写外部存储公共目录工具:

public class StorageFileUtils {
public enum MediaType{
VIDEO,
IMAGE,
AUDIO,
DOWNLOADS
}

/**
* 获取媒体文件保存的外部存储公共目录的fd,为了读,查询
* @param context
* @param fileName
* @return
*/
public static FileDescriptor getPublicFileFdForRead(Context context,MediaType type,String fileName){
DataAbilityHelper helper = DataAbilityHelper.creator(context); //api7以后,create方法替代了
Uri externalDataAbilityUri = null;
switch (type){
case VIDEO:
externalDataAbilityUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
break;
case IMAGE:
externalDataAbilityUri = AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI;
break;
case DOWNLOADS:
externalDataAbilityUri = AVStorage.Downloads.EXTERNAL_DATA_ABILITY_URI;
break;
case AUDIO:
externalDataAbilityUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;
break;
}
ResultSet rs = null;
FileDescriptor fileDescriptor = null;
try {
DataAbilityPredicates predicates =
new DataAbilityPredicates("_display_name = '" + fileName + "'");
rs = helper.query(externalDataAbilityUri,new String[]{AVStorage.Video.Media.ID},predicates);
System.out.println("rs count:" + rs.getRowCount());
if(rs != null){
while (rs.goToNextRow()) {
int mediaId = rs.getInt(rs.getColumnIndexForName(AVStorage.Video.Media.ID));
Uri uri = Uri.appendEncodedPathToUri(externalDataAbilityUri,"" + mediaId);
fileDescriptor = helper.openFile(uri, "r");
return fileDescriptor;
}
}
} catch (Exception e) {
System.out.println("rs read error!");
e.printStackTrace();
} finally {
if (rs != null) {
rs.close();
}
}
return fileDescriptor;
}

/**
* 获取媒体文件保存的外部存储公共目录的fd,为了写,插入
* @param context
* @param type
* @param name
* @return
*/
public static FileDescriptor getPublicFdForInsert(Context context,
MediaType type, String name) {
DataAbilityHelper helper = DataAbilityHelper.creator(context);
Uri externalDataAbilityUri = null;
ValuesBucket vb = new ValuesBucket();
switch (type){
case VIDEO:
externalDataAbilityUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
vb.putString(AVStorage.Video.Media.DISPLAY_NAME,name);
break;
case IMAGE:
externalDataAbilityUri = AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI;
vb.putString(AVStorage.Images.Media.DISPLAY_NAME,name);
break;
case DOWNLOADS:
externalDataAbilityUri = AVStorage.Downloads.EXTERNAL_DATA_ABILITY_URI;
vb.putString(AVStorage.Downloads.DISPLAY_NAME,name);
break;
case AUDIO:
externalDataAbilityUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;//这里uri对应目录
vb.putString(AVStorage.Audio.Media.DISPLAY_NAME,name);
break;
}
FileOutputStream outputStream = null;
FileDescriptor fd = null;
try {
int id = helper.insert(externalDataAbilityUri, vb);
System.out.println("insert rs:" + id);
Uri uri = Uri.appendEncodedPathToUri(externalDataAbilityUri, ""+id); //这个uri对应文件
outputStream = (FileOutputStream) helper.obtainOutputStream(uri);
fd = outputStream.getFD();
return fd;
} catch (Exception e) {
System.out.println(" helper.insert error!");
e.printStackTrace();
}finally {
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return fd;
}
}

视频处理核心工具类:

public class MediaHandler {
public static boolean combineTwoVideos(Context context,
FileDescriptor audioFd,
int audioStartTime,
int audioEndTime,
FileDescriptor videoFd,
int videoStartTime,
int videoEndTime,
FileDescriptor outFd) {
if(audioFd == null || videoFd==null){
System.out.println("=======audioSrcFd或者framesSrcFd文件没读到!");
return false;
}

Extractor audioExtractor = new Extractor();
int mainAudioExtractorTrackIndex = -1; //(提供音频的)视频的音频轨道号
int mainAudioMuxerTrackIndex = -1; //合成后的视频的音频轨
int mainAudioMaxInputSize = 0; //能获取的音频的最大值

Extractor frameExtractor = new Extractor();
int frameExtractorTrackIndex = -1; //(提供画面的)视频的视频轨
int frameMuxerTrackIndex = -1; //合成后的视频的视频轨
int frameMaxInputSize = 0; //能获取的视频的最大值
int frameRate = 0; //视频的帧率
long frameDuration = 0;

Muxer muxer = null; //用于合成音频与视频

try {
muxer = new Muxer(outFd,Muxer.MediaFileFormat.FORMAT_MPEG4);

//音轨信息
audioExtractor.setSource(new Source(audioFd)); //设置视频源
int audioTrackCount = audioExtractor.getTotalStreams(); //获取数据源的轨道数
//在此循环轨道数,目的是找到我们想要的音频轨
for (int i = 0; i < audioTrackCount; i++) {
Format format = audioExtractor.getStreamFormat(i);//得到指定索引的记录格式
String mimeType = format.getStringValue(Format.MIME);
System.out.println("=======mimeType:"+mimeType);
if (mimeType.startsWith("audio/")) { //找到音轨
mainAudioExtractorTrackIndex = i;
mainAudioMuxerTrackIndex = muxer.appendTrack(format); //将音轨添加到Muxer,并返回新的轨道
mainAudioMaxInputSize = format.getIntValue(Format.MAX_INPUT_SIZE);
//得到能获取的有关音频的最大值
}
}

//图像信息
frameExtractor.setSource(new Source(videoFd)); //设置视频源
int trackCount = frameExtractor.getTotalStreams();//获取数据源的轨道数
//在此循环轨道数,目的是找到我们想要的视频轨
for (int i = 0; i < trackCount; i++) {
Format vedioFormat = frameExtractor.getStreamFormat(i);
String vedioMimeType = vedioFormat.getStringValue(Format.MIME);
if (vedioMimeType.startsWith("video/")) { //找到视频轨
frameExtractorTrackIndex = i;
frameMuxerTrackIndex = muxer.appendTrack(vedioFormat); //将视频轨添加到Muxer,并返回新的轨道
frameMaxInputSize = vedioFormat.getIntValue(Format.MAX_INPUT_SIZE);
frameRate = vedioFormat.getIntValue(Format.FRAME_RATE); //获取视频的帧率
frameDuration = vedioFormat.getLongValue(Format.DURATION); //获取视频时长
}
}

muxer.start(); //开始合成
audioExtractor.specifyStream(mainAudioExtractorTrackIndex); //将提供音频的视频选择到音轨上
BufferInfo bufferInfo = new BufferInfo();
ByteBuffer audioByteBuffer = ByteBuffer.allocate(mainAudioMaxInputSize);
while (true) {
int readSampleSize = audioExtractor.readBuffer(audioByteBuffer, 0);
//检索当前编码的样本并将其存储在字节缓冲区中
long audioSampleTime = audioExtractor.getFrameTimestamp(); //获取当前展示样本的时间(单位毫秒)
if (readSampleSize < 0) { //如果没有可获取的样本则退出循环
audioExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
break;
}

if (audioSampleTime < audioStartTime*1000) { //如果样本时间小于我们想要的开始时间就快进
System.out.println("audioSampleTime < audioStartTime:"
+ audioSampleTime +","+ audioStartTime*1000);
audioExtractor.next(); //推进到下一个样本,类似快进
continue;
}
//如果样本时间大于结束时间,就退出循环
if (audioSampleTime > audioEndTime*1000) {
System.out.println("audioSampleTime > audioEndTime:" + audioSampleTime
+","+ audioEndTime*1000);
audioExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
break;
}
//设置样本编码信息
bufferInfo.size = readSampleSize;
bufferInfo.offset = 0;
bufferInfo.bufferType = audioExtractor.getFrameType();
bufferInfo.timeStamp = audioSampleTime - audioStartTime*1000;

muxer.writeBuffer(mainAudioMuxerTrackIndex, audioByteBuffer, bufferInfo); //将样本写入
audioExtractor.next(); //推进到下一个样本,类似快进
System.out.println("=======正在合成音频...");
}

frameExtractor.specifyStream(frameExtractorTrackIndex); //将提供视频图像的视频选择到视频轨上
BufferInfo vedioBufferInfo = new BufferInfo();
ByteBuffer videoByteBuffer = ByteBuffer.allocate(frameMaxInputSize);
while (true) {
int readSampleSize = frameExtractor.readBuffer(videoByteBuffer, 0);
//检索当前编码的样本并将其存储在字节缓冲区中
if (readSampleSize < 0) { //如果没有可获取的样本则退出循环
frameExtractor.unspecifyStream(frameExtractorTrackIndex);
break;
}

long videoSampleTime = frameExtractor.getFrameTimestamp(); //获取当前展示样本的时间(单位毫秒)
if (videoSampleTime < videoStartTime*1000) { //如果样本时间小于我们想要的开始时间就快进
System.out.println("videoSampleTime < videoStartTime:"
+ videoSampleTime +","+ videoStartTime*1000);
frameExtractor.next(); //推进到下一个样本,类似快进
continue;
}
//如果样本时间大于结束时间,就退出循环
if (videoSampleTime > videoEndTime*1000) {
System.out.println("videoSampleTime > videoEndTime:"
+ videoSampleTime +","+ videoEndTime*1000);
frameExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
break;
}
//设置样本编码信息
vedioBufferInfo.size = readSampleSize;
vedioBufferInfo.offset = 0;
vedioBufferInfo.bufferType = frameExtractor.getFrameType();
vedioBufferInfo.timeStamp += 1000 * 1000 / frameRate; //每帧的时间是 1/frameRate秒,需转成微秒

muxer.writeBuffer(frameMuxerTrackIndex, videoByteBuffer, vedioBufferInfo); //将样本写入
frameExtractor.next(); //推进到下一个样本,类似快进
System.out.println("=======正在合成视频...");
}
System.out.println("=======合成完毕");
return true;
}catch (Exception e){
System.out.println("==========combineTwoVideos出错了!");
return false;
}finally {
//释放资源
audioExtractor.release();
frameExtractor.release();
if (muxer != null) {
muxer.release();
}
}
}
}

毫秒转00:00:00格式字符串工具:

public class DateUtils {
private static final int ONE_SECONDS_MS = 1000;
private static final int ONE_MINS_MINUTES = 60;
private static final int NUMBER = 16;
private static final String TIME_FORMAT = "%02d";
private static final String SEMICOLON = ":";

public static String msToString(int ms) {
StringBuilder sb = new StringBuilder(NUMBER);
int seconds = ms / ONE_SECONDS_MS;
int minutes = seconds / ONE_MINS_MINUTES;
if (minutes > ONE_MINS_MINUTES) {
sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes / ONE_MINS_MINUTES));
sb.append(SEMICOLON);
sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes % ONE_MINS_MINUTES));
sb.append(SEMICOLON);
} else {
sb.append("00:");
sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes));
sb.append(SEMICOLON);
}

if (seconds > minutes * ONE_MINS_MINUTES) {
sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, seconds - minutes * ONE_MINS_MINUTES));
} else {
sb.append("00");
}
return sb.toString();
}
}

视频播放器工具:

public class PlayerHandler {
private Player videoPlayer;
private Context context;
private Runnable videoRunnable;
private boolean isFirstPlay = true;
private EventHandler handler = new EventHandler(EventRunner.create());
private PlayerStateInterface playerStateInterface;
private PlayerCurrentTimeInterface playerCurrentTimeInterface;
public static interface PlayerCurrentTimeInterface{
void updateCurrentTime(int currentTime);
}

public void setPlayerCurrentTimeInterface(PlayerCurrentTimeInterface pcti){
this.playerCurrentTimeInterface = pcti;
}

private Timer timer = new Timer();
private TimerTask timerTask = null;

public PlayerHandler(Context context) {
this.context = context;
}

public synchronized void startPlay(Source source, Surface surface) {
if(videoPlayer == null){
videoPlayer = new Player(context);
}
videoPlayer.setPlayerCallback(new VideoCallBack());
System.out.println("======isFirstPlay:"+isFirstPlay);
if(isFirstPlay){
videoRunnable = () -> firstPlay(source, surface);
handler.postTask(videoRunnable);
isFirstPlay = false;
}else{
if(!videoPlayer.isNowPlaying()){
handler.removeTask(videoRunnable);
videoRunnable = () -> play();
handler.postTask(videoRunnable);
}
}
timer.purge();
if(timerTask==null){
timerTask = new TimerTask() {
@Override
public void run() {
playerCurrentTimeInterface.updateCurrentTime(getCurrentTime());
}
};
}
timer.schedule(timerTask, 0, 1000);
}

private void firstPlay(Source source, Surface surface) {
videoPlayer.setSource(source);
videoPlayer.setVideoSurface(surface);
videoPlayer.prepare();
videoPlayer.play();
}
private void play() {
if (videoPlayer == null) {
return;
}
videoPlayer.play();
}
public synchronized void pausePlay() {
if (videoPlayer == null) {
return;
}
videoPlayer.pause();
timerTask.cancel();
timerTask = null;//如果不重新new,会报异常
}

public void rewindTo(long ms) {
if (videoPlayer == null) {
return;
}
System.out.println("ms:" + ms);
videoPlayer.rewindTo(ms*1000);
}

public void release() {
if (videoPlayer != null) {
videoPlayer.stop();
videoPlayer.release();
videoPlayer = null;
timer.cancel();
timer = null;
}
}

public int getCurrentTime(){
if (videoPlayer == null) {
return 0;
}
return videoPlayer.getCurrentTime();
}

public static interface PlayerStateInterface {
void updateTotalTime(int totalTime);
}

public void setPlayerStateInterface(PlayerStateInterface psi){
this.playerStateInterface = psi;
}
private class VideoCallBack implements Player.IPlayerCallback{

@Override
public void onPrepared() {
playerStateInterface.updateTotalTime(videoPlayer.getDuration());
}

@Override
public void onMessage(int i, int i1) {

}

@Override
public void onError(int i, int i1) {

}

@Override
public void onResolutionChanged(int i, int i1) {

}

@Override
public void onPlayBackComplete() {

}

@Override
public void onRewindToComplete() {
}

@Override
public void onBufferingChange(int i) {

}

@Override
public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {

}

@Override
public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {

}
}

}

业务逻辑实现:

MainAbilitySlice:从外部存储公共目录里读取视频文件,调整到分了合成页面或剪切页面:

public class MainAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
initComponents();
}
private void initComponents() {
Component goCutBtn = findComponentById(ResourceTable.Id_go_cut_btn);
goCutBtn.setClickedListener(this::goCutBtnFunc);
Component goSeparateBtn = findComponentById(ResourceTable.Id_go_separate_btn);
goSeparateBtn.setClickedListener(this::goSeparateBtnFunc);
}
private void goSeparateBtnFunc(Component component) {
Intent intent = new Intent();
Operation build = new Intent.OperationBuilder()
.withBundleName(getBundleName())
.withDeviceId("")
.withAbilityName(SeparateAbility.class.getName())
.build();
intent.setParam("separateFiles",new String[]{"audioFile.mp4","videoFile.mp4"});
intent.setOperation(build);
startAbility(intent);
}

private void goCutBtnFunc(Component component) {
//这里可以做的更灵活一定,做一个文件列表读取sd卡上的视频文件,选择要处理的拿出来它的文件名
Intent intent = new Intent();
Operation build = new Intent.OperationBuilder()
.withBundleName(getBundleName())
.withDeviceId("")
.withAbilityName(CutAbility.class.getName())
.build();
intent.setParam("cutFile","cut.mp4");
intent.setOperation(build);
startAbility(intent);
}

@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

视频分离合成的逻辑:

public class SeparateAbilitySlice extends AbilitySlice {
private String[] separateFiles;
private FileDescriptor audioFd;
private FileDescriptor videoFd;
private SurfaceProvider audioSurfaceProvider;
private Surface audioSurface;
private SurfaceProvider videoSurfaceProvider;
private Surface videoSurface;
private PlayerHandler audioPlayerHandler;
private PlayerHandler videoPlayerHandler;
private boolean audioClick = false;
private boolean videoClick = false;
private int audioTotalTime;
private int videoTotalTime;
private Slider audioProgressBar;
private Slider videoProgressBar;
private Button startTime1,startTime2,endTime1,endTime2;
private Text currentTime1,currentTime2;
private int startAudioTime1,startVideoTimt2,endAudioTime1,endVideoTime2;
private Button separateBtn;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_separate);
//初始化组件
initCompoents();
//拿到参数和分离合成文件
initParam(intent);
//使用播放器播放视频
initSurfaceProvider();
//设置组件的监听
initListeners();
}

private void initListeners() {
audioProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
@Override
public void onProgressUpdated(Slider slider, int value, boolean isB) {
getUITaskDispatcher().asyncDispatch(() ->
currentTime1.setText(DateUtils.msToString(value)));
}

@Override
public void onTouchStart(Slider slider) {

}

@Override
public void onTouchEnd(Slider slider) {
if (slider.getProgress() == audioTotalTime) {
audioPlayerHandler.release();
} else {
audioPlayerHandler.rewindTo(slider.getProgress());
}
}
});

videoProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
@Override
public void onProgressUpdated(Slider slider, int value, boolean isB) {
getUITaskDispatcher().asyncDispatch(() ->
currentTime2.setText(DateUtils.msToString(value)));
}

@Override
public void onTouchStart(Slider slider) {

}

@Override
public void onTouchEnd(Slider slider) {
if (slider.getProgress() == videoTotalTime) {
videoPlayerHandler.release();
} else {
videoPlayerHandler.rewindTo(slider.getProgress());
}
}
});
startTime1.setClickedListener(component -> {
startAudioTime1 = audioPlayerHandler.getCurrentTime();
startTime1.setText(DateUtils.msToString(startAudioTime1));
});

startTime2.setClickedListener(component -> {
startVideoTimt2 = videoPlayerHandler.getCurrentTime();
startTime2.setText(DateUtils.msToString(startVideoTimt2));
});

endTime1.setClickedListener(component -> {
endAudioTime1 = audioPlayerHandler.getCurrentTime();
endTime1.setText(DateUtils.msToString(endAudioTime1));
});

endTime2.setClickedListener(component -> {
endVideoTime2 = videoPlayerHandler.getCurrentTime();
endTime2.setText(DateUtils.msToString(endVideoTime2));
});

separateBtn.setClickedListener(component -> {
FileDescriptor outFd = StorageFileUtils.getPublicFdForInsert(this
, StorageFileUtils.MediaType.VIDEO, "combineFile.mp4");
boolean rs = MediaHandler.combineTwoVideos(this,audioFd,startAudioTime1,endAudioTime1
,videoFd,startVideoTimt2,endVideoTime2,outFd);
if (rs){
getUITaskDispatcher().asyncDispatch(()->{
new ToastDialog(this).setText("合成完成").show();
});
}
});
}
private void initCompoents() {
audioProgressBar = (Slider) findComponentById(ResourceTable.Id_progress1);
videoProgressBar = (Slider) findComponentById(ResourceTable.Id_progress2);
startTime1 = (Button)findComponentById(ResourceTable.Id_start_time_btn1);
startTime2 = (Button)findComponentById(ResourceTable.Id_start_time_btn2);
endTime1 = (Button)findComponentById(ResourceTable.Id_end_time_btn1);
endTime2 = (Button)findComponentById(ResourceTable.Id_end_time_btn2);
currentTime1 = (Text) findComponentById(ResourceTable.Id_current_time1);
currentTime2 = (Text) findComponentById(ResourceTable.Id_current_time2);
separateBtn = (Button)findComponentById(ResourceTable.Id_separate_btn);
}
private void initSurfaceProvider() {
audioSurfaceProvider = new SurfaceProvider(this);
audioSurfaceProvider.getSurfaceOps().get().addCallback(new AudioSurfaceCallBack());
audioSurfaceProvider.pinToZTop(true);
DirectionalLayout directionalLayout1 =
(DirectionalLayout) findComponentById(ResourceTable.Id_play_directionalLayout1);
directionalLayout1.addComponent(audioSurfaceProvider);

videoSurfaceProvider = new SurfaceProvider(this);
videoSurfaceProvider.getSurfaceOps().get().addCallback(new VedioSurfaceCallBack());
videoSurfaceProvider.pinToZTop(true);
DirectionalLayout directionalLayout2 =
(DirectionalLayout) findComponentById(ResourceTable.Id_play_directionalLayout2);
directionalLayout2.addComponent(videoSurfaceProvider);
}
private class AudioSurfaceCallBack implements SurfaceOps.Callback{
@Override
public void surfaceCreated(SurfaceOps surfaceOps) {
System.out.println("======audio surfaceCreated");
if (audioSurfaceProvider.getSurfaceOps().isPresent()) {
audioSurface = audioSurfaceProvider.getSurfaceOps().get().getSurface();
initAudioPlayer(audioSurface);
}
}
@Override
public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
@Override
public void surfaceDestroyed(SurfaceOps surfaceOps) {}
}

private class VedioSurfaceCallBack implements SurfaceOps.Callback{
@Override
public void surfaceCreated(SurfaceOps surfaceOps) {
System.out.println("======video surfaceCreated");
if (videoSurfaceProvider.getSurfaceOps().isPresent()) {
videoSurface = videoSurfaceProvider.getSurfaceOps().get().getSurface();
initVideoPlayer(videoSurface);
}
}
@Override
public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
@Override
public void surfaceDestroyed(SurfaceOps surfaceOps) {}
}

private void initAudioPlayer(Surface audioSurface) {
System.out.println("======initAudioPlayer");
audioPlayerHandler = new PlayerHandler(getApplicationContext());
audioPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
@Override
public void updateTotalTime(int totalTime) {
System.out.println("======slice totalTime:" + totalTime);
audioTotalTime = totalTime;
getUITaskDispatcher().asyncDispatch(()->{
Text time1 = (Text)findComponentById(ResourceTable.Id_end_time1);
time1.setText(DateUtils.msToString(totalTime));
audioProgressBar.setMaxValue(totalTime);
});
}
});
audioPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
@Override
public void updateCurrentTime(int currentTime) {
getUITaskDispatcher().asyncDispatch(()->{
audioProgressBar.setProgressValue(currentTime);
});
}
});
Source source = new Source(audioFd);
audioSurfaceProvider.setClickedListener(component -> {
if(!audioClick){
audioClick = !audioClick;
audioPlayerHandler.startPlay(source, audioSurface);
}else{
audioClick = !audioClick;
audioPlayerHandler.pausePlay();
}
});
}
private void initVideoPlayer(Surface audioSurface) {
System.out.println("======initVideoPlayer");
videoPlayerHandler = new PlayerHandler(getApplicationContext());
videoPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
@Override
public void updateTotalTime(int totalTime) {
videoTotalTime = totalTime;
getUITaskDispatcher().asyncDispatch(()->{
Text time2 = (Text)findComponentById(ResourceTable.Id_end_time2);
time2.setText(DateUtils.msToString(totalTime));
videoProgressBar.setMaxValue(totalTime);
});
}
});
videoPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
@Override
public void updateCurrentTime(int currentTime) {
videoProgressBar.setProgressValue(currentTime);
}
});
Source source = new Source(videoFd);
videoSurfaceProvider.setClickedListener(component -> {
if(!videoClick){
videoClick = !videoClick;
videoPlayerHandler.startPlay(source, videoSurface);
}else{
videoClick = !videoClick;
videoPlayerHandler.pausePlay();
}
});
}

private void initParam(Intent intent) {
if(intent != null){
separateFiles = intent.getStringArrayParam("separateFiles");
System.out.println("=====" + Arrays.toString(separateFiles));
if(separateFiles.length == 2){
audioFd = StorageFileUtils.getPublicFileFdForRead(
this,
StorageFileUtils.MediaType.VIDEO,
separateFiles[0]
);
videoFd = StorageFileUtils.getPublicFileFdForRead(
this,
StorageFileUtils.MediaType.VIDEO,
separateFiles[1]
);
}
}
}
@Override
protected void onStop() {
super.onStop();
audioPlayerHandler.release();
videoSurfaceProvider.release();
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

视频截取逻辑代码:

public class CutAbilitySlice extends AbilitySlice {
private Slider videoProgressBar;
private Button startTime;
private Button endTime;
private Button cutBtn;
private String cutFile;
private FileDescriptor videoFd;
private SurfaceProvider videoSurfaceProvider;
private Surface videoSurface;
private PlayerHandler videoPlayerHandler;
private int videoTotalTime;
private boolean videoClick;
private Text currentTimeText;
private int startVideoTime;
private int endVideoTime;

@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_cut);
//初始化组件
initCompoents();
//拿到参数和分离合成文件
initParam(intent);
//使用播放器播放视频
initSurfaceProvider();
//设置组件的监听
initListeners();
}
private void initCompoents() {
videoProgressBar = (Slider) findComponentById(ResourceTable.Id_progress);
startTime = (Button)findComponentById(ResourceTable.Id_start_time_btn);
endTime = (Button)findComponentById(ResourceTable.Id_end_time_btn);
cutBtn = (Button)findComponentById(ResourceTable.Id_cut_btn);
currentTimeText = (Text) findComponentById(ResourceTable.Id_current_time);
}

private void initParam(Intent intent) {
if(intent != null){
cutFile = intent.getStringParam("cutFile");
videoFd = StorageFileUtils.getPublicFileFdForRead(
this,
StorageFileUtils.MediaType.VIDEO,
cutFile
);
}
}
private void initSurfaceProvider() {
videoSurfaceProvider = new SurfaceProvider(this);
videoSurfaceProvider.getSurfaceOps().get().addCallback(new VedioSurfaceCallBack());
videoSurfaceProvider.pinToZTop(true);
DirectionalLayout directionalLayout =
(DirectionalLayout) findComponentById(ResourceTable.Id_cut_play_directionalLayout);
directionalLayout.addComponent(videoSurfaceProvider);
}
private class VedioSurfaceCallBack implements SurfaceOps.Callback{
@Override
public void surfaceCreated(SurfaceOps surfaceOps) {
System.out.println("======video surfaceCreated");
if (videoSurfaceProvider.getSurfaceOps().isPresent()) {
videoSurface = videoSurfaceProvider.getSurfaceOps().get().getSurface();
initVideoPlayer(videoSurface);
}
}
@Override
public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
@Override
public void surfaceDestroyed(SurfaceOps surfaceOps) {}
}
private void initVideoPlayer(Surface audioSurface) {
System.out.println("======initVideoPlayer");
videoPlayerHandler = new PlayerHandler(getApplicationContext());
videoPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
@Override
public void updateTotalTime(int totalTime) {
videoTotalTime = totalTime;
getUITaskDispatcher().asyncDispatch(()->{
Text time = (Text)findComponentById(ResourceTable.Id_end_time);
time.setText(DateUtils.msToString(totalTime));
videoProgressBar.setMaxValue(totalTime);
});
}
});
videoPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
@Override
public void updateCurrentTime(int currentTime) {
videoProgressBar.setProgressValue(currentTime);
}
});
Source source = new Source(videoFd);
videoSurfaceProvider.setClickedListener(component -> {
if(!videoClick){
videoClick = !videoClick;
videoPlayerHandler.startPlay(source, videoSurface);
}else{
videoClick = !videoClick;
videoPlayerHandler.pausePlay();
}
});
}
private void initListeners() {
videoProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
@Override
public void onProgressUpdated(Slider slider, int value, boolean isB) {
getUITaskDispatcher().asyncDispatch(() ->
currentTimeText.setText(DateUtils.msToString(value)));
}

@Override
public void onTouchStart(Slider slider) {

}

@Override
public void onTouchEnd(Slider slider) {
if (slider.getProgress() == videoTotalTime) {
videoPlayerHandler.release();
} else {
videoPlayerHandler.rewindTo(slider.getProgress());
}
}
});
startTime.setClickedListener(component -> {
startVideoTime = videoPlayerHandler.getCurrentTime();
startTime.setText(DateUtils.msToString(startVideoTime));
});
endTime.setClickedListener(component -> {
endVideoTime = videoPlayerHandler.getCurrentTime();
endTime.setText(DateUtils.msToString(endVideoTime));
});

cutBtn.setClickedListener(component -> {
FileDescriptor outFd = StorageFileUtils.getPublicFdForInsert(this
, StorageFileUtils.MediaType.VIDEO, "cutFileRs.mp4");
boolean rs = MediaHandler.combineTwoVideos(this,videoFd,startVideoTime,endVideoTime
,videoFd,startVideoTime,endVideoTime,outFd);
if (rs){
getUITaskDispatcher().asyncDispatch(()->{
new ToastDialog(this).setText("剪切完成").show();
});
}
});
}
@Override
protected void onStop() {
super.onStop();
videoSurfaceProvider.release();
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://harmonyos.51cto.com​

HarmonyOS实现音视频分离合成和截取

© 版权声明

相关文章